Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.13% covered (warning)
68.13%
1785 / 2620
42.33% covered (danger)
42.33%
80 / 189
CRAP
0.00% covered (danger)
0.00%
0 / 5
SeedDMS_Core_Document
71.73% covered (warning)
71.73%
931 / 1298
43.75% covered (danger)
43.75%
35 / 80
7967.48
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 clearCache
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSearchFields
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 getInstanceByData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 applyDecorators
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 getDir
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setKeywords
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 hasCategory
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getCategories
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 setCategories
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
13.28
 addCategories
72.41% covered (warning)
72.41%
21 / 29
0.00% covered (danger)
0.00%
0 / 1
15.02
 removeCategories
60.00% covered (warning)
60.00%
12 / 20
0.00% covered (danger)
0.00%
0 / 1
14.18
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 isDescendant
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFolder
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFolder
71.43% covered (warning)
71.43%
25 / 35
0.00% covered (danger)
0.00%
0 / 1
16.94
 getOwner
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setOwner
65.22% covered (warning)
65.22%
15 / 23
0.00% covered (danger)
0.00%
0 / 1
14.21
 getDefaultAccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setDefaultAccess
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 inheritsAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInheritAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritAccess
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 expires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getExpires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setExpires
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 hasExpired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 verifyLastestContentExpriry
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
9.89
 isLocked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocked
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 getLockingUser
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getSequence
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSequence
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 clearAccessList
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getAccessList
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
12
 addAccess
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 changeAccess
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 removeAccess
81.25% covered (warning)
81.25%
13 / 16
0.00% covered (danger)
0.00%
0 / 1
6.24
 getAccessMode
72.50% covered (warning)
72.50%
29 / 40
0.00% covered (danger)
0.00%
0 / 1
40.06
 getGroupAccessMode
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
7.12
 getNotifyList
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
10.02
 cleanNotifyList
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 addNotify
55.00% covered (warning)
55.00%
22 / 40
0.00% covered (danger)
0.00%
0 / 1
43.34
 removeNotify
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
8.51
 addContent
51.04% covered (warning)
51.04%
49 / 96
0.00% covered (danger)
0.00%
0 / 1
197.65
 replaceContent
72.55% covered (warning)
72.55%
37 / 51
0.00% covered (danger)
0.00%
0 / 1
24.70
 getContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
7.10
 getContentByVersion
71.43% covered (warning)
71.43%
15 / 21
0.00% covered (danger)
0.00%
0 / 1
13.82
 isLatestContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getLatestContent
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getLatestContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
8.12
 _removeContent
38.30% covered (danger)
38.30%
36 / 94
0.00% covered (danger)
0.00%
0 / 1
322.76
 removeContent
62.96% covered (warning)
62.96%
17 / 27
0.00% covered (danger)
0.00%
0 / 1
23.96
 getDocumentLink
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
8.35
 getDocumentLinks
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
10
 getReverseDocumentLinks
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
8.01
 addDocumentLink
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 removeDocumentLink
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getDocumentFile
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getDocumentFiles
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 addDocumentFile
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
11.65
 removeDocumentFile
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
13.70
 remove
58.21% covered (warning)
58.21%
39 / 67
0.00% covered (danger)
0.00%
0 / 1
80.21
 __getApproversList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadAccessList
69.09% covered (warning)
69.09%
38 / 55
0.00% covered (danger)
0.00%
0 / 1
45.96
 getFolderList
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 repair
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 getUsedDiskSpace
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getTimeline
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
8.03
 transferToUser
62.50% covered (warning)
62.50%
15 / 24
0.00% covered (danger)
0.00%
0 / 1
7.90
SeedDMS_Core_DocumentContent
64.35% covered (warning)
64.35%
742 / 1153
21.74% covered (danger)
21.74%
15 / 69
12912.13
0.00% covered (danger)
0.00%
0 / 1
 verifyStatus
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
20
 __construct
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
3
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
6.13
 getFileSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFileSize
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getChecksum
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealChecksum
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setChecksum
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setFileType
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
6.40
 setMimeType
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 setComment
47.06% covered (danger)
47.06%
8 / 17
0.00% covered (danger)
0.00%
0 / 1
17.50
 getStatus
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
6.04
 getStatusLog
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
 setStatus
91.18% covered (success)
91.18%
31 / 34
0.00% covered (danger)
0.00%
0 / 1
16.18
 rewriteStatusLog
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
56
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
198.00
 getReviewers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getReviewStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getReviewLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteReviewLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getApprovers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getApprovalStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getApproveLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteApprovalLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 addIndReviewer
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpReviewer
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setReviewByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeReview
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setReviewByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 addIndApprover
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpApprover
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setApprovalByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeApproval
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setApprovalByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 delIndReviewer
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
9.63
 delGrpReviewer
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 delIndApprover
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
9.37
 delGrpApprover
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 setWorkflowState
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 getWorkflowState
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 setWorkflow
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
6.77
 getWorkflow
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 rewriteWorkflowLog
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 rewindWorkflow
75.00% covered (warning)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
3.14
 removeWorkflow
86.21% covered (warning)
86.21%
25 / 29
0.00% covered (danger)
0.00%
0 / 1
8.17
 getParentWorkflow
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 runSubWorkflow
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 returnFromSubWorkflow
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
90
 triggerWorkflowTransitionIsAllowed
66.67% covered (warning)
66.67%
18 / 27
0.00% covered (danger)
0.00%
0 / 1
19.26
 executeWorkflowTransitionIsAllowed
52.78% covered (warning)
52.78%
19 / 36
0.00% covered (danger)
0.00%
0 / 1
42.96
 triggerWorkflowTransition
65.00% covered (warning)
65.00%
13 / 20
0.00% covered (danger)
0.00%
0 / 1
12.47
 enterNextState
76.00% covered (warning)
76.00%
19 / 25
0.00% covered (danger)
0.00%
0 / 1
12.67
 getWorkflowLog
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 getLastWorkflowLog
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 needsWorkflowAction
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 repair
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
SeedDMS_Core_DocumentLink
85.00% covered (warning)
85.00%
17 / 20
87.50% covered (warning)
87.50%
7 / 8
13.57
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_DocumentFile
88.31% covered (warning)
88.31%
68 / 77
86.96% covered (warning)
86.96%
20 / 23
42.55
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVersion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPublic
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_AddContentResultSet
22.41% covered (danger)
22.41%
13 / 58
33.33% covered (danger)
33.33%
3 / 9
865.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addReviewer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 addApprover
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 setStatus
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReviewers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
 getApprovers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of a document in the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL2
10 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
11 *             Uwe Steinmann <uwe@steinmann.cx>
12 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
13 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
14 * @version    Release: @package_version@
15 */
16
17/**
18 * The different states a document can be in
19 */
20/*
21 * Document is in review state. A document is in review state when
22 * it needs to be reviewed by a user or group.
23 */
24define("S_DRAFT_REV", 0);
25
26/*
27 * Document is in approval state. A document is in approval state when
28 * it needs to be approved by a user or group.
29 */
30define("S_DRAFT_APP", 1);
31
32/*
33 * Document is released. A document is in release state either when
34 * it needs no review or approval after uploaded or has been reviewed
35 * and/or approved.
36 */
37define("S_RELEASED", 2);
38
39/*
40 * Document is in workflow. A document is in workflow if a workflow
41 * has been started and has not reached a final state.
42 */
43define("S_IN_WORKFLOW", 3);
44
45/*
46 * Document was rejected. A document is in rejected state when
47 * the review failed or approval was not given.
48 */
49define("S_REJECTED", -1);
50
51/*
52 * Document is obsolete. A document can be obsoleted once it was
53 * released.
54 */
55define("S_OBSOLETE", -2);
56
57/*
58 * Document is expired. A document expires when the expiration date
59 * is reached
60 */
61define("S_EXPIRED", -3);
62
63/*
64 * Lowest and highest status that may be set
65 */
66define("S_LOWEST_STATUS", -3);
67define("S_HIGHEST_STATUS", 3);
68
69/**
70 * The different states a workflow log can be in. This is used in
71 * all tables tblDocumentXXXLog
72 */
73/*
74 * workflow is in a neutral status waiting for action of user
75 */
76define("S_LOG_WAITING", 0);
77
78/*
79 * workflow has been successful ended. The document content has been
80 * approved, reviewed, aknowledged or revised
81 */
82define("S_LOG_ACCEPTED", 1);
83
84/*
85 * workflow has been unsuccessful ended. The document content has been
86 * rejected
87 */
88define("S_LOG_REJECTED", -1);
89
90/*
91 * user has been removed from workflow. This can be for different reasons
92 * 1. the user has been actively removed from the workflow, 2. the user has
93 * been deleted.
94 */
95define("S_LOG_USER_REMOVED", -2);
96
97/*
98 * workflow is sleeping until reactivation. The workflow has been set up
99 * but not started. This is only valid for the revision workflow, which
100 * may run over and over again.
101 */
102define("S_LOG_SLEEPING", -3);
103
104/**
105 * Class to represent a document in the document management system
106 *
107 * A document in SeedDMS is a collection of content elements which are
108 * similar to a file in a regular file system.
109 * Documents may have any number of content elements
110 * ({@see SeedDMS_Core_DocumentContent}). These content elements are often
111 * called versions ordered in a timely manner. The most recent content element
112 * is the current version of the document.
113 *
114 * Documents can be linked to other documents, can have attached files,
115 * can be assigned to a category and have additional attributes.
116 * The document content can be anything that can be stored in a regular
117 * file.
118 *
119 * @category   DMS
120 * @package    SeedDMS_Core
121 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
122 *             Uwe Steinmann <uwe@steinmann.cx>
123 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
124 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
125 * @version    Release: @package_version@
126 */
127class SeedDMS_Core_Document extends SeedDMS_Core_Object { /* {{{ */
128    /**
129     * @var string name of document
130     */
131    protected $_name;
132
133    /**
134     * @var string comment of document
135     */
136    protected $_comment;
137
138    /**
139     * @var integer unix timestamp of creation date
140     */
141    protected $_date;
142
143    /**
144     * @var integer id of user who is the owner
145     */
146    protected $_ownerID;
147
148    /**
149     * @var object user who is the owner
150     */
151    protected $_owner;
152
153    /**
154     * @var integer id of folder this document belongs to
155     */
156    protected $_folderID;
157
158    /**
159     * @var object parent folder this document belongs to
160     */
161    protected $_parent;
162
163    /**
164     * @var integer timestamp of expiration date
165     */
166    protected $_expires;
167
168    /**
169     * @var boolean true if access is inherited, otherwise false
170     */
171    protected $_inheritAccess;
172
173    /**
174     * @var integer default access if access rights are not inherited
175     */
176    protected $_defaultAccess;
177
178    /**
179     * @var array list of notifications for users and groups
180     */
181    protected $_readAccessList;
182
183    /**
184     * @var array list of notifications for users and groups
185     */
186    public $_notifyList;
187
188    /**
189     * @var boolean true if document is locked, otherwise false
190     */
191    protected $_locked;
192
193    /**
194     * @var object user who has locked the document
195     */
196    protected $_lockingUser;
197
198    /**
199     * @var string list of keywords
200     */
201    protected $_keywords;
202
203    /**
204     * @var SeedDMS_Core_DocumentCategory[] list of categories
205     */
206    protected $_categories;
207
208    /**
209     * @var integer position of document within the parent folder
210     */
211    protected $_sequence;
212
213    /**
214     * @var SeedDMS_Core_DocumentContent temp. storage for latestcontent
215     */
216    protected $_latestContent;
217
218    /**
219     * @var array temp. storage for content
220     */
221    protected $_content;
222
223    /**
224     * @var SeedDMS_Core_Folder
225     */
226    protected $_folder;
227
228    /** @var array of SeedDMS_Core_UserAccess and SeedDMS_Core_GroupAccess */
229    protected $_accessList;
230
231    /**
232     * @var array
233     */
234    protected $_documentLinks;
235
236    /**
237     * @var array
238     */
239    protected $_documentFiles;
240
241    public function __construct($id, $name, $comment, $date, $expires, $ownerID, $folderID, $inheritAccess, $defaultAccess, $locked, $keywords, $sequence) { /* {{{ */
242        parent::__construct($id);
243        $this->_name = $name;
244        $this->_comment = $comment;
245        $this->_date = $date;
246        $this->_expires = $expires;
247        $this->_ownerID = $ownerID;
248        $this->_folderID = $folderID;
249        $this->_inheritAccess = $inheritAccess ? true : false;
250        $this->_defaultAccess = $defaultAccess;
251        $this->_locked = ($locked == null || $locked == '' ? -1 : $locked);
252        $this->_lockingUser = null;
253        $this->_keywords = $keywords;
254        $this->_sequence = $sequence;
255        $this->_categories = array();
256        $this->_notifyList = array();
257        $this->_latestContent = null;
258        $this->_content = null;
259        /* Cache */
260        $this->clearCache();
261    } /* }}} */
262
263    /**
264     * Clear cache of this instance.
265     *
266     * The result of some expensive database actions (e.g. get all subfolders
267     * or documents) will be saved in a class variable to speed up consecutive
268     * calls of the same method. If a second call of the same method shall not
269     * use the cache, then it must be cleared.
270     *
271     */
272    public function clearCache() { /* {{{ */
273        $this->_parent = null;
274        $this->_owner = null;
275        $this->_documentLinks = null;
276        $this->_documentFiles = null;
277        $this->_content = null;
278        $this->_accessList = null;
279        $this->_notifyList = array();
280        $this->_readAccessList = array();
281    } /* }}} */
282
283    /**
284     * Check if this object is of type 'document'.
285     *
286     * @param string $type type of object
287     */
288    public function isType($type) { /* {{{ */
289        return $type == 'document';
290    } /* }}} */
291
292    /**
293     * Return an array of database fields which are used for searching
294     * a term entered in the database search form
295     *
296     * @param SeedDMS_Core_DMS $dms
297     * @param array $searchin integer list of search scopes (2=name, 3=comment,
298     * 4=attributes)
299     * @return array list of database fields
300     */
301    public static function getSearchFields($dms, $searchin) { /* {{{ */
302        $db = $dms->getDB();
303
304        $searchFields = array();
305        if (in_array(1, $searchin)) {
306            $searchFields[] = "`tblDocuments`.`keywords`";
307        }
308        if (in_array(2, $searchin)) {
309            $searchFields[] = "`tblDocuments`.`name`";
310        }
311        if (in_array(3, $searchin)) {
312            $searchFields[] = "`tblDocuments`.`comment`";
313            $searchFields[] = "`tblDocumentContent`.`comment`";
314        }
315        if (in_array(4, $searchin)) {
316            $searchFields[] = "`tblDocumentAttributes`.`value`";
317            $searchFields[] = "`tblDocumentContentAttributes`.`value`";
318        }
319        if (in_array(5, $searchin)) {
320            $searchFields[] = $db->castToText("`tblDocuments`.`id`");
321        }
322
323        return $searchFields;
324    } /* }}} */
325
326    /**
327     * Return a folder by its database record
328     *
329     * @param array $resArr array of folder data as returned by database
330     * @param SeedDMS_Core_DMS $dms
331     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists
332     */
333    public static function getInstanceByData($resArr, $dms) { /* {{{ */
334        $classname = $dms->getClassname('document');
335        /** @var SeedDMS_Core_Document $document */
336        $document = new $classname($resArr["id"], $resArr["name"], $resArr["comment"], $resArr["date"], $resArr["expires"], $resArr["owner"], $resArr["folder"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr['lock'], $resArr["keywords"], $resArr["sequence"]);
337        $document->setDMS($dms);
338        $document = $document->applyDecorators();
339        return $document;
340    } /* }}} */
341
342    /**
343     * Return an document by its id
344     *
345     * @param integer $id id of document
346     * @param SeedDMS_Core_DMS $dms
347     * @return bool|SeedDMS_Core_Document instance of SeedDMS_Core_Document if document exists, null
348     * if document does not exist, false in case of error
349     */
350    public static function getInstance($id, $dms) { /* {{{ */
351        $db = $dms->getDB();
352
353//        $queryStr = "SELECT * FROM `tblDocuments` WHERE `id` = " . (int) $id;
354        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `id` = " . (int) $id;
355        if ($dms->checkWithinRootDir)
356            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
357        $resArr = $db->getResultArray($queryStr);
358        if (is_bool($resArr) && $resArr == false)
359            return false;
360        if (count($resArr) != 1)
361            return null;
362        $resArr = $resArr[0];
363
364        $resArr['lock'] = !$resArr['lock'] ? -1 : $resArr['lock'];
365
366        return self::getInstanceByData($resArr, $dms);
367    } /* }}} */
368
369    /**
370     * Apply decorators
371     *
372     * @return object final object after all decorators has been applied
373     */
374    public function applyDecorators() { /* {{{ */
375        if ($decorators = $this->_dms->getDecorators('document')) {
376            $s = $this;
377            foreach ($decorators as $decorator) {
378                $s = new $decorator($s);
379            }
380            return $s;
381        } else {
382            return $this;
383        }
384    } /* }}} */
385
386    /**
387     * Return the directory of the document in the file system relativ
388     * to the contentDir
389     *
390     * @return string directory of document
391     */
392    public function getDir() { /* {{{ */
393        if ($this->_dms->maxDirID) {
394            $dirid = (int) (($this->_id-1) / $this->_dms->maxDirID) + 1;
395            return $dirid.DIRECTORY_SEPARATOR.$this->_id.DIRECTORY_SEPARATOR;
396        } else {
397            return $this->_id.DIRECTORY_SEPARATOR;
398        }
399    } /* }}} */
400
401    /**
402     * Return the name of the document
403     *
404     * @return string name of document
405     */
406    public function getName() { return $this->_name; }
407
408    /**
409     * Set the name of the document
410     *
411     * @param $newName string new name of document
412     * @return bool
413     */
414    public function setName($newName) { /* {{{ */
415        $db = $this->_dms->getDB();
416
417        /* Check if 'onPreSetName' callback is set */
418        if (isset($this->_dms->callbacks['onPreSetName'])) {
419            foreach ($this->_dms->callbacks['onPreSetName'] as $callback) {
420                $ret = call_user_func($callback[0], $callback[1], $this, $newName);
421                if (is_bool($ret))
422                    return $ret;
423            }
424        }
425
426        $queryStr = "UPDATE `tblDocuments` SET `name` = ".$db->qstr($newName)." WHERE `id` = ". $this->_id;
427        if (!$db->getResult($queryStr))
428            return false;
429
430        $oldName = $this->_name;
431        $this->_name = $newName;
432
433        /* Check if 'onPostSetName' callback is set */
434        if (isset($this->_dms->callbacks['onPostSetName'])) {
435            foreach ($this->_dms->callbacks['onPostSetName'] as $callback) {
436                $ret = call_user_func($callback[0], $callback[1], $this, $oldName);
437                if (is_bool($ret))
438                    return $ret;
439            }
440        }
441
442        return true;
443    } /* }}} */
444
445    /**
446     * Return the comment of the document
447     *
448     * @return string comment of document
449     */
450    public function getComment() { return $this->_comment; }
451
452    /**
453     * Set the comment of the document
454     *
455     * @param $newComment string new comment of document
456     * @return bool
457     */
458    public function setComment($newComment) { /* {{{ */
459        $db = $this->_dms->getDB();
460
461        /* Check if 'onPreSetComment' callback is set */
462        if (isset($this->_dms->callbacks['onPreSetComment'])) {
463            foreach ($this->_dms->callbacks['onPreSetComment'] as $callback) {
464                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
465                if (is_bool($ret))
466                    return $ret;
467            }
468        }
469
470        $queryStr = "UPDATE `tblDocuments` SET `comment` = ".$db->qstr($newComment)." WHERE `id` = ". $this->_id;
471        if (!$db->getResult($queryStr))
472            return false;
473
474        $oldComment = $this->_comment;
475        $this->_comment = $newComment;
476
477        /* Check if 'onPostSetComment' callback is set */
478        if (isset($this->_dms->callbacks['onPostSetComment'])) {
479            foreach ($this->_dms->callbacks['onPostSetComment'] as $callback) {
480                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
481                if (is_bool($ret))
482                    return $ret;
483            }
484        }
485
486        return true;
487    } /* }}} */
488
489    /**
490     * @return string
491     */
492    public function getKeywords() { return $this->_keywords; }
493
494    /**
495     * @param string $newKeywords
496     * @return bool
497     */
498    public function setKeywords($newKeywords) { /* {{{ */
499        $db = $this->_dms->getDB();
500
501        /* Check if 'onPreSetKeywords' callback is set */
502        if (isset($this->_dms->callbacks['onPreSetKeywords'])) {
503            foreach ($this->_dms->callbacks['onPreSetKeywords'] as $callback) {
504                $ret = call_user_func($callback[0], $callback[1], $this, $newKeywords);
505                if (is_bool($ret))
506                    return $ret;
507            }
508        }
509
510        $queryStr = "UPDATE `tblDocuments` SET `keywords` = ".$db->qstr($newKeywords)." WHERE `id` = ". $this->_id;
511        if (!$db->getResult($queryStr))
512            return false;
513
514        $oldKeywords = $this->_keywords;
515        $this->_keywords = $newKeywords;
516
517        /* Check if 'onPostSetKeywords' callback is set */
518        if (isset($this->_dms->callbacks['onPostSetKeywords'])) {
519            foreach ($this->_dms->callbacks['onPostSetKeywords'] as $callback) {
520                $ret = call_user_func($callback[0], $callback[1], $this, $oldKeywords);
521                if (is_bool($ret))
522                    return $ret;
523            }
524        }
525
526        return true;
527    } /* }}} */
528
529    /**
530     * Check if document has a given category
531     *
532     * @param SeedDMS_Core_DocumentCategory $cat
533     * @return bool true if document has category, otherwise false
534     */
535    public function hasCategory($cat) { /* {{{ */
536        $db = $this->_dms->getDB();
537
538        if (!$cat)
539            return false;
540
541        $queryStr = "SELECT * FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id." AND `categoryID`=".$cat->getId();
542        $resArr = $db->getResultArray($queryStr);
543        if (!$resArr)
544            return false;
545
546        return true;
547    } /* }}} */
548
549    /**
550     * Retrieve a list of all categories this document belongs to
551     *
552     * @return bool|SeedDMS_Core_DocumentCategory[]
553     */
554    public function getCategories() { /* {{{ */
555        $db = $this->_dms->getDB();
556
557        if (!$this->_categories) {
558            $queryStr = "SELECT * FROM `tblCategory` WHERE `id` IN (SELECT `categoryID` FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id.")";
559            $resArr = $db->getResultArray($queryStr);
560            if (is_bool($resArr) && !$resArr)
561                return false;
562
563            $this->_categories = [];
564            foreach ($resArr as $row) {
565                $cat = new SeedDMS_Core_DocumentCategory($row['id'], $row['name']);
566                $cat->setDMS($this->_dms);
567                $this->_categories[] = $cat;
568            }
569        }
570        return $this->_categories;
571    } /* }}} */
572
573    /**
574     * Set a list of categories for the document
575     *
576     * This method will delete currently assigned categories and sets new
577     * categories.
578     *
579     * @param SeedDMS_Core_DocumentCategory[] $newCategories list of category objects
580     * @return bool
581     */
582    public function setCategories($newCategories) { /* {{{ */
583        $db = $this->_dms->getDB();
584
585        /* Check if 'onPreSetCategories' callback is set */
586        if (isset($this->_dms->callbacks['onPreSetCategories'])) {
587            foreach ($this->_dms->callbacks['onPreSetCategories'] as $callback) {
588                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
589                if (is_bool($ret))
590                    return $ret;
591            }
592        }
593
594        $db->startTransaction();
595        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id;
596        if (!$db->getResult($queryStr)) {
597            $db->rollbackTransaction();
598            return false;
599        }
600
601        foreach ($newCategories as $cat) {
602            $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
603            if (!$db->getResult($queryStr)) {
604                $db->rollbackTransaction();
605                return false;
606            }
607        }
608
609        $db->commitTransaction();
610
611        $oldCategories = $this->_categories;
612        $this->_categories = $newCategories;
613
614        /* Check if 'onPostSetCategories' callback is set */
615        if (isset($this->_dms->callbacks['onPostSetCategories'])) {
616            foreach ($this->_dms->callbacks['onPostSetCategories'] as $callback) {
617                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
618                if (is_bool($ret))
619                    return $ret;
620            }
621        }
622
623        return true;
624    } /* }}} */
625
626    /**
627     * Add a list of categories to the document
628     *
629     * This method will add a list of new categories to the document.
630     *
631     * @param array $newCategories list of category objects
632     */
633    public function addCategories($newCategories) { /* {{{ */
634        $db = $this->_dms->getDB();
635
636        /* Check if 'onPreAddCategories' callback is set */
637        if (isset($this->_dms->callbacks['onPreAddCategories'])) {
638            foreach ($this->_dms->callbacks['onPreAddCategories'] as $callback) {
639                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
640                if (is_bool($ret))
641                    return $ret;
642            }
643        }
644
645        if (!$this->_categories)
646            $this->getCategories();
647
648        $catids = array();
649        foreach ($this->_categories as $cat)
650            $catids[] = $cat->getID();
651
652        $db->startTransaction();
653        $ncat = array(); // Array containing actually added new categories
654        foreach ($newCategories as $cat) {
655            if (!in_array($cat->getID(), $catids)) {
656                $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
657                if (!$db->getResult($queryStr)) {
658                    $db->rollbackTransaction();
659                    return false;
660                }
661                $ncat[] = $cat;
662            }
663        }
664        $db->commitTransaction();
665
666        $oldCategories = $this->_categories;
667        $this->_categories = array_merge($this->_categories, $ncat);
668
669        /* Check if 'onPostAddCategories' callback is set */
670        if (isset($this->_dms->callbacks['onPostAddCategories'])) {
671            foreach ($this->_dms->callbacks['onPostAddCategories'] as $callback) {
672                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
673                if (is_bool($ret))
674                    return $ret;
675            }
676        }
677
678        return true;
679    } /* }}} */
680
681    /**
682     * Remove a list of categories from the document
683     *
684     * This method will remove a list of assigned categories to the document.
685     *
686     * @param array $newCategories list of category objects
687     */
688    public function removeCategories($categories) { /* {{{ */
689        $db = $this->_dms->getDB();
690
691        /* Check if 'onPreRemoveCategories' callback is set */
692        if (isset($this->_dms->callbacks['onPreRemoveCategories'])) {
693            foreach ($this->_dms->callbacks['onPreRemoveCategories'] as $callback) {
694                $ret = call_user_func($callback[0], $callback[1], $this, $categories);
695                if (is_bool($ret))
696                    return $ret;
697            }
698        }
699
700        $catids = array();
701        foreach ($categories as $cat)
702            $catids[] = $cat->getID();
703
704        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id ." AND `categoryID` IN (".implode(',', $catids).")";
705        if (!$db->getResult($queryStr)) {
706            return false;
707        }
708
709        $oldCategories = $this->_categories;
710        $this->_categories = null;
711
712        /* Check if 'onPostRemoveCategories' callback is set */
713        if (isset($this->_dms->callbacks['onPostRemoveCategories'])) {
714            foreach ($this->_dms->callbacks['onPostRemoveCategories'] as $callback) {
715                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
716                if (is_bool($ret))
717                    return $ret;
718            }
719        }
720
721        return true;
722    } /* }}} */
723
724    /**
725     * Return creation date of the document
726     *
727     * @return integer unix timestamp of creation date
728     */
729    public function getDate() { /* {{{ */
730        return $this->_date;
731    } /* }}} */
732
733    /**
734     * Set creation date of the document
735     *
736     * @param integer $date timestamp of creation date. If false then set it
737     * to the current timestamp
738     * @return boolean true on success
739     */
740    public function setDate($date) { /* {{{ */
741        $db = $this->_dms->getDB();
742
743        if (!$date)
744            $date = time();
745        else {
746            if (!is_numeric($date))
747                return false;
748        }
749
750        $queryStr = "UPDATE `tblDocuments` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
751        if (!$db->getResult($queryStr))
752            return false;
753        $this->_date = $date;
754        return true;
755    } /* }}} */
756
757    /**
758     * Check, if this document is a child of a given folder
759     *
760     * @param object $folder parent folder
761     * @return boolean true if document is a direct child of the given folder
762     */
763    public function isDescendant($folder) { /* {{{ */
764        /* First check if the parent folder is folder looking for */
765        if ($this->getFolder()->getID() == $folder->getID())
766            return true;
767        /* Second, check for the parent folder of this document to be
768         * below the given folder
769         */
770        if ($this->getFolder()->isDescendant($folder))
771            return true;
772        return false;
773    } /* }}} */
774
775    /**
776     * Return the parent folder of the document
777     *
778     * @see SeedDMS_Core_Document::getFolder()
779     *
780     * @return SeedDMS_Core_Folder parent folder
781     */
782    public function getParent() { /* {{{ */
783        return $this->getFolder();
784    } /* }}} */
785
786    /**
787     * Return the parent folder of the document
788     *
789     * @return SeedDMS_Core_Folder parent folder
790     */
791    public function getFolder() { /* {{{ */
792        if (!isset($this->_folder))
793            $this->_folder = $this->_dms->getFolder($this->_folderID);
794        return $this->_folder;
795    } /* }}} */
796
797    /**
798     * Set folder of a document
799     *
800     * This method basically moves a document from a folder to another
801     * folder.
802     *
803     * @param SeedDMS_Core_Folder $newFolder
804     * @return boolean false in case of an error, otherwise true
805     */
806    public function setParent($newFolder) { /* {{{ */
807        return $this->setFolder($newFolder);
808    } /* }}} */
809
810    /**
811     * Set folder of a document
812     *
813     * This method basically moves a document from a folder to another
814     * folder.
815     *
816     * @param SeedDMS_Core_Folder $newFolder
817     * @return boolean false in case of an error, otherwise true
818     */
819    public function setFolder($newFolder) { /* {{{ */
820        $db = $this->_dms->getDB();
821
822        if (!$newFolder)
823            return false;
824
825        if (!$newFolder->isType('folder'))
826            return false;
827
828        /* Check if 'onPreSetFolder' callback is set */
829        if (isset($this->_dms->callbacks['onPreSetFolder'])) {
830            foreach ($this->_dms->callbacks['onPreSetFolder'] as $callback) {
831                $ret = call_user_func($callback[0], $callback[1], $this, $newFolder);
832                if (is_bool($ret))
833                    return $ret;
834            }
835        }
836
837        $db->startTransaction();
838
839        $queryStr = "UPDATE `tblDocuments` SET `folder` = " . $newFolder->getID() . " WHERE `id` = ". $this->_id;
840        if (!$db->getResult($queryStr)) {
841            $db->rollbackTransaction();
842            return false;
843        }
844
845        // Make sure that the folder search path is also updated.
846        $path = $newFolder->getPath();
847        $flist = "";
848        /** @var SeedDMS_Core_Folder[] $path */
849        foreach ($path as $f) {
850            $flist .= ":".$f->getID();
851        }
852        if (strlen($flist)>1) {
853            $flist .= ":";
854        }
855        $queryStr = "UPDATE `tblDocuments` SET `folderList` = '" . $flist . "' WHERE `id` = ". $this->_id;
856        if (!$db->getResult($queryStr)) {
857            $db->rollbackTransaction();
858            return false;
859        }
860
861        $db->commitTransaction();
862
863        $oldFolder = $this->_folder;
864        $this->_folderID = $newFolder->getID();
865        $this->_folder = $newFolder;
866
867        /* Check if 'onPostSetFolder' callback is set */
868        if (isset($this->_dms->callbacks['onPostSetFolder'])) {
869            foreach ($this->_dms->callbacks['onPostSetFolder'] as $callback) {
870                $ret = call_user_func($callback[0], $callback[1], $this, $oldFolder);
871                if (is_bool($ret))
872                    return $ret;
873            }
874        }
875
876        return true;
877    } /* }}} */
878
879    /**
880     * Return owner of document
881     *
882     * @return SeedDMS_Core_User owner of document as an instance of {@see SeedDMS_Core_User}
883     */
884    public function getOwner() { /* {{{ */
885        if (!isset($this->_owner))
886            $this->_owner = $this->_dms->getUser($this->_ownerID);
887        return $this->_owner;
888    } /* }}} */
889
890    /**
891     * Set owner of a document
892     *
893     * @param SeedDMS_Core_User $newOwner new owner
894     * @return boolean true if successful otherwise false
895     */
896    public function setOwner($newOwner) { /* {{{ */
897        $db = $this->_dms->getDB();
898
899        if (!$newOwner)
900            return false;
901
902        if (!$newOwner->isType('user'))
903            return false;
904
905        /* Check if 'onPreSetOwner' callback is set */
906        if (isset($this->_dms->callbacks['onPreSetOwner'])) {
907            foreach ($this->_dms->callbacks['onPreSetOwner'] as $callback) {
908                $ret = call_user_func($callback[0], $callback[1], $this, $newOwner);
909                if (is_bool($ret))
910                    return $ret;
911            }
912        }
913
914        $queryStr = "UPDATE `tblDocuments` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id;
915        if (!$db->getResult($queryStr))
916            return false;
917
918        $oldOwner = $this->_owner;
919        $this->_ownerID = $newOwner->getID();
920        $this->_owner = $newOwner;
921
922        $this->_readAccessList = array();
923
924        /* Check if 'onPostSetOwner' callback is set */
925        if (isset($this->_dms->callbacks['onPostSetOwner'])) {
926            foreach ($this->_dms->callbacks['onPostSetOwner'] as $callback) {
927                $ret = call_user_func($callback[0], $callback[1], $this, $oldOwner);
928                if (is_bool($ret))
929                    return $ret;
930            }
931        }
932
933        return true;
934    } /* }}} */
935
936    /**
937     * @return bool|int
938     */
939    public function getDefaultAccess() { /* {{{ */
940        if ($this->inheritsAccess()) {
941            $res = $this->getFolder();
942            if (!$res) return false;
943            return $this->_folder->getDefaultAccess();
944        }
945        return $this->_defaultAccess;
946    } /* }}} */
947
948    /**
949     * Set default access mode
950     *
951     * This method sets the default access mode and also removes all notifiers which
952     * will not have read access anymore. Setting a default access mode will only
953     * have an immediate effect if the access rights are not inherited, otherwise
954     * it just updates the database record of the document and once the
955     * inheritance is turn off the default access mode will take effect.
956     *
957     * @param integer     $mode    access mode
958     * @param bool|string $noclean set to true if notifier list shall not be clean up
959     *
960     * @return bool
961     */
962    public function setDefaultAccess($mode, $noclean = false) { /* {{{ */
963        $db = $this->_dms->getDB();
964
965        if ($mode < M_LOWEST_RIGHT || $mode > M_HIGHEST_RIGHT)
966            return false;
967
968        $queryStr = "UPDATE `tblDocuments` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id;
969        if (!$db->getResult($queryStr))
970            return false;
971
972        $this->_defaultAccess = $mode;
973        $this->_readAccessList = array();
974
975        /* Setting the default access mode does not have any effect if access
976         * is still inherited. In that case there is no need to clean the
977         * notification list.
978         */
979        if (!$noclean && !$this->_inheritAccess)
980            $this->cleanNotifyList();
981
982        return true;
983    } /* }}} */
984
985    /**
986     * @return bool
987     */
988    public function inheritsAccess() { return $this->_inheritAccess; }
989
990    /**
991     * This is supposed to be a replacement for inheritsAccess()
992     *
993     * @return bool
994     */
995    public function getInheritAccess() { return $this->_inheritAccess; }
996
997    /**
998     * Set inherited access mode
999     *
1000     * Setting inherited access mode will set or unset the internal flag which
1001     * controls if the access mode is inherited from the parent folder or not.
1002     * It will not modify the
1003     * access control list for the current object. It will remove all
1004     * notifications of users which do not even have read access anymore
1005     * after setting or unsetting inherited access.
1006     *
1007     * @param boolean $inheritAccess set to true for setting and false for
1008     *        unsetting inherited access mode
1009     * @param boolean $noclean set to true if notifier list shall not be clean up
1010     * @return boolean true if operation was successful otherwise false
1011     */
1012    public function setInheritAccess($inheritAccess, $noclean = false) { /* {{{ */
1013        $db = $this->_dms->getDB();
1014
1015        $queryStr = "UPDATE `tblDocuments` SET `inheritAccess` = " . ($inheritAccess ? "1" : "0") . " WHERE `id` = " . $this->_id;
1016        if (!$db->getResult($queryStr))
1017            return false;
1018
1019        $this->_inheritAccess = ($inheritAccess ? true : false);
1020        $this->_readAccessList = array();
1021
1022        if (!$noclean)
1023            $this->cleanNotifyList();
1024
1025        return true;
1026    } /* }}} */
1027
1028    /**
1029     * Check if document expires
1030     *
1031     * @return boolean true if document has expiration date set, otherwise false
1032     */
1033    public function expires() { /* {{{ */
1034        if (intval($this->_expires) == 0)
1035            return false;
1036        else
1037            return true;
1038    } /* }}} */
1039
1040    /**
1041     * Get expiration time of document
1042     *
1043     * @return integer/boolean expiration date as unix timestamp or false
1044     */
1045    public function getExpires() { /* {{{ */
1046        if (intval($this->_expires) == 0)
1047            return false;
1048        else
1049            return $this->_expires;
1050    } /* }}} */
1051
1052    /**
1053     * Set expiration date as unix timestamp
1054     *
1055     * @param integer $expires unix timestamp of expiration date
1056     * @return bool
1057     */
1058    public function setExpires($expires) { /* {{{ */
1059        $db = $this->_dms->getDB();
1060
1061        $expires = (!$expires) ? 0 : $expires;
1062
1063        if ($expires == $this->_expires) {
1064            // No change is necessary.
1065            return true;
1066        }
1067
1068        $queryStr = "UPDATE `tblDocuments` SET `expires` = " . (int) $expires . " WHERE `id` = " . $this->_id;
1069        if (!$db->getResult($queryStr))
1070            return false;
1071
1072        $this->_expires = $expires;
1073        return true;
1074    } /* }}} */
1075
1076    /**
1077     * Check if the document has expired
1078     *
1079     * The method expects to database field 'expired' to hold the timestamp
1080     * of the start of day at which end the document expires. The document will
1081     * expire if that day is over. Hence, a document will *not*
1082     * be expired during the day of expiration but at the end of that day
1083     *
1084     * @return boolean true if document has expired otherwise false
1085     */
1086    public function hasExpired() { /* {{{ */
1087        if (intval($this->_expires) == 0) return false;
1088        if (time()>=$this->_expires+24*60*60) return true;
1089        return false;
1090    } /* }}} */
1091
1092    /**
1093     * Check if the document has expired and set the status accordingly
1094     *
1095     * It will also recalculate the status if the current status is
1096     * set to S_EXPIRED but the document isn't actually expired.
1097     * The method will update the document status log database table
1098     * if needed.
1099     * FIXME: some left over reviewers/approvers are in the way if
1100     * no workflow is set and traditional workflow mode is on. In that
1101     * case the status is set to S_DRAFT_REV or S_DRAFT_APP
1102     *
1103     * @return boolean true if status has changed
1104     */
1105    public function verifyLastestContentExpriry() { /* {{{ */
1106        $lc = $this->getLatestContent();
1107        if ($lc) {
1108            $st = $lc->getStatus();
1109
1110            if (($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP || $st["status"]==S_IN_WORKFLOW || $st["status"]==S_RELEASED) && $this->hasExpired()) {
1111                return $lc->setStatus(S_EXPIRED, "", $this->getOwner());
1112            }
1113            elseif ($st["status"]==S_EXPIRED && !$this->hasExpired()) {
1114                $lc->verifyStatus(true, $this->getOwner());
1115                return true;
1116            }
1117        }
1118        return false;
1119    } /* }}} */
1120
1121    /**
1122     * Check if document is locked
1123     *
1124     * @return boolean true if locked otherwise false
1125     */
1126    public function isLocked() { return $this->_locked != -1; }
1127
1128    /**
1129     * Lock or unlock document
1130     *
1131     * @param SeedDMS_Core_User|bool $falseOrUser user object for locking or false for unlocking
1132     * @return boolean true if operation was successful otherwise false
1133     */
1134    public function setLocked($falseOrUser) { /* {{{ */
1135        $db = $this->_dms->getDB();
1136
1137        $lockUserID = -1;
1138        if (is_bool($falseOrUser) && !$falseOrUser) {
1139            $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = ".$this->_id;
1140        }
1141        elseif (is_object($falseOrUser)) {
1142            $queryStr = "INSERT INTO `tblDocumentLocks` (`document`, `userID`) VALUES (".$this->_id.", ".$falseOrUser->getID().")";
1143            $lockUserID = $falseOrUser->getID();
1144        }
1145        else {
1146            return false;
1147        }
1148        if (!$db->getResult($queryStr)) {
1149            return false;
1150        }
1151        unset($this->_lockingUser);
1152        $this->_locked = $lockUserID;
1153        return true;
1154    } /* }}} */
1155
1156    /**
1157     * Get the user currently locking the document
1158     *
1159     * @return SeedDMS_Core_User|bool user have a lock
1160     */
1161    public function getLockingUser() { /* {{{ */
1162        if (!$this->isLocked())
1163            return false;
1164
1165        if (!isset($this->_lockingUser))
1166            $this->_lockingUser = $this->_dms->getUser($this->_locked);
1167        return $this->_lockingUser;
1168    } /* }}} */
1169
1170    /**
1171     * @return float
1172     */
1173    public function getSequence() { return $this->_sequence; }
1174
1175    /**
1176     * @param float $seq
1177     * @return bool
1178     */
1179    public function setSequence($seq) { /* {{{ */
1180        $db = $this->_dms->getDB();
1181
1182        $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id;
1183        if (!$db->getResult($queryStr))
1184            return false;
1185
1186        $this->_sequence = $seq;
1187        return true;
1188    } /* }}} */
1189
1190    /**
1191     * Delete all entries for this document from the access control list
1192     *
1193     * @param boolean $noclean set to true if notifier list shall not be clean up
1194     * @return boolean true if operation was successful otherwise false
1195     */
1196    public function clearAccessList($noclean = false) { /* {{{ */
1197        $db = $this->_dms->getDB();
1198
1199        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1200        if (!$db->getResult($queryStr))
1201            return false;
1202
1203        unset($this->_accessList);
1204        $this->_readAccessList = array();
1205
1206        if (!$noclean)
1207            $this->cleanNotifyList();
1208
1209        return true;
1210    } /* }}} */
1211
1212    /**
1213     * Returns a list of access privileges
1214     *
1215     * If the document inherits the access privileges from the parent folder
1216     * those will be returned.
1217     * $mode and $op can be set to restrict the list of returned access
1218     * privileges. If $mode is set to M_ANY no restriction will apply
1219     * regardless of the value of $op. The returned array contains a list
1220     * of {@see SeedDMS_Core_UserAccess} and
1221     * {@see SeedDMS_Core_GroupAccess} objects. Even if the document
1222     * has no access list the returned array contains the two elements
1223     * 'users' and 'groups' which are than empty. The methode returns false
1224     * if the function fails.
1225     *
1226     * @param int $mode access mode (defaults to M_ANY)
1227     * @param int|string $op operation (defaults to O_EQ)
1228     * @return bool|array
1229     */
1230    public function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */
1231        $db = $this->_dms->getDB();
1232
1233        if ($this->inheritsAccess()) {
1234            $res = $this->getFolder();
1235            if (!$res) return false;
1236            return $this->_folder->getAccessList($mode, $op);
1237        }
1238
1239        if (!isset($this->_accessList[$mode])) {
1240            if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) {
1241                return false;
1242            }
1243            $modeStr = "";
1244            if ($mode!=M_ANY) {
1245                $modeStr = " AND `mode`".$op.(int)$mode;
1246            }
1247            $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1248                " AND `target` = " . $this->_id . $modeStr . " ORDER BY `targetType`";
1249            $resArr = $db->getResultArray($queryStr);
1250            if (is_bool($resArr) && !$resArr)
1251                return false;
1252
1253            $this->_accessList[$mode] = array("groups" => array(), "users" => array());
1254            foreach ($resArr as $row) {
1255                if ($row["userID"] != -1)
1256                    array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), (int) $row["mode"]));
1257                else //if ($row["groupID"] != -1)
1258                    array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), (int) $row["mode"]));
1259            }
1260        }
1261
1262        return $this->_accessList[$mode];
1263    } /* }}} */
1264
1265    /**
1266     * Add access right to document
1267     *
1268     * This method may change in the future. Instead of passing a flag
1269     * and a user/group id a user or group object will be expected.
1270     * Starting with version 5.1.25 this method will first check if there
1271     * is already an access right for the user/group.
1272     *
1273     * @param integer $mode access mode
1274     * @param integer $userOrGroupID id of user or group
1275     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1276     *        user otherwise it will be considered a group id
1277     * @return bool true on success, otherwise false
1278     */
1279    public function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */
1280        $db = $this->_dms->getDB();
1281
1282        if ($mode < M_NONE || $mode > M_ALL)
1283            return false;
1284
1285        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1286
1287        /* Adding a second access right will return false */
1288        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1289                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ".$userOrGroupID;
1290        $resArr = $db->getResultArray($queryStr);
1291        if (is_bool($resArr) || $resArr)
1292            return false;
1293
1294        $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES
1295                    (".$this->_id.", ".T_DOCUMENT.", " . (int) $userOrGroupID . ", " .(int) $mode. ")";
1296        if (!$db->getResult($queryStr))
1297            return false;
1298
1299        unset($this->_accessList);
1300        $this->_readAccessList = array();
1301
1302        // Update the notify list, if necessary.
1303        if ($mode == M_NONE) {
1304            $this->removeNotify($userOrGroupID, $isUser);
1305        }
1306
1307        return true;
1308    } /* }}} */
1309
1310    /**
1311     * Change access right of document
1312     *
1313     * This method may change in the future. Instead of passing a flag
1314     * and a user/group id a user or group object will be expected.
1315     *
1316     * @param integer $newMode access mode
1317     * @param integer $userOrGroupID id of user or group
1318     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1319     *        user otherwise it will be considered a group id
1320     * @return bool true on success, otherwise false
1321     */
1322    public function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */
1323        $db = $this->_dms->getDB();
1324
1325        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1326
1327        /* Get the old access right */
1328        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1329                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1330        $resArr = $db->getResultArray($queryStr);
1331        if (!$resArr)
1332            return false;
1333
1334        $oldmode = $resArr[0]['mode'];
1335
1336        $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_DOCUMENT." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1337        if (!$db->getResult($queryStr))
1338            return false;
1339
1340        unset($this->_accessList);
1341        $this->_readAccessList = array();
1342
1343        // Update the notify list, if necessary.
1344        if ($newMode == M_NONE) {
1345            $this->removeNotify($userOrGroupID, $isUser);
1346        }
1347
1348        return $oldmode;
1349    } /* }}} */
1350
1351    /**
1352     * Remove access rights for a user or group
1353     *
1354     * @param integer $userOrGroupID ID of user or group
1355     * @param boolean $isUser true if $userOrGroupID is a user id, false if it
1356     *        is a group id.
1357     * @return boolean true on success, otherwise false
1358     */
1359    public function removeAccess($userOrGroupID, $isUser) { /* {{{ */
1360        $db = $this->_dms->getDB();
1361
1362        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1363
1364        /* Get the old access right */
1365        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1366                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1367        $resArr = $db->getResultArray($queryStr);
1368        if (!$resArr)
1369            return false;
1370
1371        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID;
1372        if (!$db->getResult($queryStr))
1373            return false;
1374
1375        unset($this->_accessList);
1376        $this->_readAccessList = array();
1377
1378        // Update the notify list, if the user looses access rights.
1379        $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID)));
1380        if ($mode == M_NONE) {
1381            $this->removeNotify($userOrGroupID, $isUser);
1382        }
1383
1384        return true;
1385    } /* }}} */
1386
1387    /**
1388     * Returns the greatest access privilege for a given user
1389     *
1390     * This method returns the access mode for a given user. An administrator
1391     * and the owner of the folder has unrestricted access. A guest user has
1392     * read only access or no access if access rights are further limited
1393     * by access control lists. All other users have access rights according
1394     * to the access control lists or the default access. This method will
1395     * recursive check for access rights of parent folders if access rights
1396     * are inherited.
1397     *
1398     * The function searches the access control list for entries of
1399     * user $user. If it finds more than one entry it will return the
1400     * one allowing the greatest privileges, but user rights will always
1401     * precede group rights. If there is no entry in the
1402     * access control list, it will return the default access mode.
1403     * The function takes inherited access rights into account.
1404     * For a list of possible access rights see @file inc.AccessUtils.php
1405     *
1406     * Having access on a document does not necessarily mean the document
1407     * content is accessible too. Accessing the content is checked by
1408     * {@see SeedDMS_Core_DocumentContent::getAccessMode()} which calls
1409     * a callback function defined by the application. If the callback
1410     * function is not set, access on the content is always granted.
1411     *
1412     * Before checking the access in the method itself a callback 'onCheckAccessDocument'
1413     * is called. If it returns a value > 0, then this will be returned by this
1414     * method without any further checks. The optional paramater $context
1415     * will be passed as a third parameter to the callback. It contains
1416     * the operation for which the access mode is retrieved. It is for example
1417     * set to 'removeDocument' if the access mode is used to check for sufficient
1418     * permission on deleting a document.
1419     *
1420     * @param $user object instance of class SeedDMS_Core_User
1421     * @param string $context context in which the access mode is requested
1422     * @return integer access mode
1423     */
1424    public function getAccessMode($user, $context = '') { /* {{{ */
1425        if (!$user)
1426            return M_NONE;
1427
1428        /* Check if 'onCheckAccessDocument' callback is set */
1429        if (isset($this->_dms->callbacks['onCheckAccessDocument'])) {
1430            foreach ($this->_dms->callbacks['onCheckAccessDocument'] as $callback) {
1431                if (($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) {
1432                    return $ret;
1433                }
1434            }
1435        }
1436
1437        /* Administrators have unrestricted access */
1438        if ($user->isAdmin()) return M_ALL;
1439
1440        /* The owner of the document has unrestricted access */
1441        if ($user->getID() == $this->_ownerID) return M_ALL;
1442
1443        if ($this->_dms->memcache) {
1444            $ck = "am:d".$this->_id.":".$user->getId().":".$context;
1445            if ($cobj = $this->_dms->memcache->get($ck))
1446                return $cobj;
1447        }
1448
1449        /* Check ACLs */
1450        $accessList = $this->getAccessList();
1451        if (!$accessList) return false;
1452
1453        /** @var SeedDMS_Core_UserAccess $userAccess */
1454        foreach ($accessList["users"] as $userAccess) {
1455            if ($userAccess->getUserID() == $user->getID()) {
1456                $mode = $userAccess->getMode();
1457                if ($user->isGuest()) {
1458                    if ($mode >= M_READ) $mode = M_READ;
1459                }
1460                if ($this->_dms->memcache)
1461                     $this->_dms->memcache->set($ck, $mode, 600);
1462                return $mode;
1463            }
1464        }
1465
1466        /* Get the highest right defined by a group */
1467        if ($accessList['groups']) {
1468            $mode = 0;
1469            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1470            foreach ($accessList["groups"] as $groupAccess) {
1471                if ($user->isMemberOfGroup($groupAccess->getGroup())) {
1472                    if ($groupAccess->getMode() > $mode)
1473                        $mode = $groupAccess->getMode();
1474                }
1475            }
1476            if ($mode) {
1477                if ($user->isGuest()) {
1478                    if ($mode >= M_READ) $mode = M_READ;
1479                }
1480                if ($this->_dms->memcache)
1481                     $this->_dms->memcache->set($ck, $mode, 600);
1482                return $mode;
1483            }
1484        }
1485
1486        $mode = $this->getDefaultAccess();
1487        if ($user->isGuest()) {
1488            if ($mode >= M_READ) $mode = M_READ;
1489        }
1490        if ($this->_dms->memcache)
1491             $this->_dms->memcache->set($ck, $mode, 600);
1492        return $mode;
1493    } /* }}} */
1494
1495    /**
1496     * Returns the greatest access privilege for a given group
1497     *
1498     * This method searches the access control list for entries of
1499     * group $group. If it finds more than one entry it will return the
1500     * one allowing the greatest privileges. If there is no entry in the
1501     * access control list, it will return the default access mode.
1502     * The function takes inherited access rights into account.
1503     * For a list of possible access rights see @file inc.AccessUtils.php
1504     *
1505     * @param SeedDMS_Core_Group $group object instance of class SeedDMS_Core_Group
1506     * @return integer access mode
1507     */
1508    public function getGroupAccessMode($group) { /* {{{ */
1509        $highestPrivileged = M_NONE;
1510
1511        //ACLs durchforsten
1512        $foundInACL = false;
1513        $accessList = $this->getAccessList();
1514        if (!$accessList)
1515            return false;
1516
1517        /** @var SeedDMS_Core_GroupAccess $groupAccess */
1518        foreach ($accessList["groups"] as $groupAccess) {
1519            if ($groupAccess->getGroupID() == $group->getID()) {
1520                $foundInACL = true;
1521                if ($groupAccess->getMode() > $highestPrivileged)
1522                    $highestPrivileged = $groupAccess->getMode();
1523                if ($highestPrivileged == M_ALL) // max access right -> skip the rest
1524                    return $highestPrivileged;
1525            }
1526        }
1527
1528        if ($foundInACL)
1529            return $highestPrivileged;
1530
1531        //Standard-Berechtigung verwenden
1532        return $this->getDefaultAccess();
1533    } /* }}} */
1534
1535    /**
1536     * Returns a list of all notifications
1537     *
1538     * The returned list has two elements called 'users' and 'groups'. Each one
1539     * is an array itself countaining objects of class SeedDMS_Core_User and
1540     * SeedDMS_Core_Group.
1541     *
1542     * @param integer $type type of notification (not yet used)
1543     * @param bool $incdisabled set to true if disabled user shall be included
1544     * @return array|bool
1545     */
1546    public function getNotifyList($type = 0, $incdisabled = false) { /* {{{ */
1547        if (empty($this->_notifyList)) {
1548            $db = $this->_dms->getDB();
1549
1550            $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1551            $resArr = $db->getResultArray($queryStr);
1552            if (is_bool($resArr) && $resArr == false)
1553                return false;
1554
1555            $this->_notifyList = array("groups" => array(), "users" => array());
1556            foreach ($resArr as $row)
1557            {
1558                if ($row["userID"] != -1) {
1559                    $u = $this->_dms->getUser($row["userID"]);
1560                    if ($u && (!$u->isDisabled() || $incdisabled))
1561                        array_push($this->_notifyList["users"], $u);
1562                } else { //if ($row["groupID"] != -1)
1563                    $g = $this->_dms->getGroup($row["groupID"]);
1564                    if ($g)
1565                        array_push($this->_notifyList["groups"], $g);
1566                }
1567            }
1568        }
1569        return $this->_notifyList;
1570    } /* }}} */
1571
1572    /**
1573     * Make sure only users/groups with read access are in the notify list
1574     *
1575     */
1576    public function cleanNotifyList() { /* {{{ */
1577        // If any of the notification subscribers no longer have read access,
1578        // remove their subscription.
1579        if (empty($this->_notifyList))
1580            $this->getNotifyList();
1581
1582        /* Make a copy of both notifier lists because removeNotify will empty
1583         * $this->_notifyList and the second foreach will not work anymore.
1584         */
1585        /** @var SeedDMS_Core_User[] $nusers */
1586        $nusers = $this->_notifyList["users"];
1587        /** @var SeedDMS_Core_Group[] $ngroups */
1588        $ngroups = $this->_notifyList["groups"];
1589        foreach ($nusers as $u) {
1590            if ($this->getAccessMode($u) < M_READ) {
1591                $this->removeNotify($u->getID(), true);
1592            }
1593        }
1594        foreach ($ngroups as $g) {
1595            if ($this->getGroupAccessMode($g) < M_READ) {
1596                $this->removeNotify($g->getID(), false);
1597            }
1598        }
1599    } /* }}} */
1600
1601    /**
1602     * Add a user/group to the notification list
1603     *
1604     * This method does not check if the currently logged in user
1605     * is allowed to add a notification. This must be checked by the calling
1606     * application.
1607     *
1608     * @param $userOrGroupID integer id of user or group to add
1609     * @param $isUser integer 1 if $userOrGroupID is a user,
1610     *                0 if $userOrGroupID is a group
1611     * @return integer  0: Update successful.
1612     *                 -1: Invalid User/Group ID.
1613     *                 -2: Target User / Group does not have read access.
1614     *                 -3: User is already subscribed.
1615     *                 -4: Database / internal error.
1616     */
1617    public function addNotify($userOrGroupID, $isUser) { /* {{{ */
1618        $db = $this->_dms->getDB();
1619
1620        $userOrGroup = ($isUser ? "`userID`" : "`groupID`");
1621
1622        /* Verify that user / group exists. */
1623        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1624        if (!is_object($obj)) {
1625            return -1;
1626        }
1627
1628        /* Verify that the requesting user has permission to add the target to
1629         * the notification system.
1630         */
1631        /*
1632         * The calling application should enforce the policy on who is allowed
1633         * to add someone to the notification system. If is shall remain here
1634         * the currently logged in user should be passed to this function
1635         *
1636        GLOBAL $user;
1637        if ($user->isGuest()) {
1638            return -2;
1639        }
1640        if (!$user->isAdmin()) {
1641            if ($isUser) {
1642                if ($user->getID() != $obj->getID()) {
1643                    return -2;
1644                }
1645            }
1646            else {
1647                if (!$obj->isMember($user)) {
1648                    return -2;
1649                }
1650            }
1651        }
1652         */
1653
1654        /* Verify that target user / group has read access to the document. */
1655        if ($isUser) {
1656            // Users are straightforward to check.
1657            if ($this->getAccessMode($obj) < M_READ) {
1658                return -2;
1659            }
1660        }
1661        else {
1662            // Groups are a little more complex.
1663            if ($this->getDefaultAccess() >= M_READ) {
1664                // If the default access is at least READ-ONLY, then just make sure
1665                // that the current group has not been explicitly excluded.
1666                $acl = $this->getAccessList(M_NONE, O_EQ);
1667                $found = false;
1668                /** @var SeedDMS_Core_GroupAccess $group */
1669                foreach ($acl["groups"] as $group) {
1670                    if ($group->getGroupID() == $userOrGroupID) {
1671                        $found = true;
1672                        break;
1673                    }
1674                }
1675                if ($found) {
1676                    return -2;
1677                }
1678            }
1679            else {
1680                // The default access is restricted. Make sure that the group has
1681                // been explicitly allocated access to the document.
1682                $acl = $this->getAccessList(M_READ, O_GTEQ);
1683                if (is_bool($acl)) {
1684                    return -4;
1685                }
1686                $found = false;
1687                /** @var SeedDMS_Core_GroupAccess $group */
1688                foreach ($acl["groups"] as $group) {
1689                    if ($group->getGroupID() == $userOrGroupID) {
1690                        $found = true;
1691                        break;
1692                    }
1693                }
1694                if (!$found) {
1695                    return -2;
1696                }
1697            }
1698        }
1699        /* Check to see if user/group is already on the list. */
1700        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1701            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
1702            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
1703        $resArr = $db->getResultArray($queryStr);
1704        if (is_bool($resArr)) {
1705            return -4;
1706        }
1707        if (count($resArr)>0) {
1708            return -3;
1709        }
1710
1711        $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_DOCUMENT . ", " . (int) $userOrGroupID . ")";
1712        if (!$db->getResult($queryStr))
1713            return -4;
1714
1715        unset($this->_notifyList);
1716        return 0;
1717    } /* }}} */
1718
1719    /**
1720     * Remove a user or group from the notification list
1721     *
1722     * This method does not check if the currently logged in user
1723     * is allowed to remove a notification. This must be checked by the calling
1724     * application.
1725     *
1726     * @param integer $userOrGroupID id of user or group
1727     * @param boolean $isUser boolean true if a user is passed in $userOrGroupID, false
1728     *        if a group is passed in $userOrGroupID
1729     * @param integer $type type of notification (0 will delete all) Not used yet!
1730     * @return integer 0 if operation was succesful
1731     *                 -1 if the userid/groupid is invalid
1732     *                 -3 if the user/group is already subscribed
1733     *                 -4 in case of an internal database error
1734     */
1735    public function removeNotify($userOrGroupID, $isUser, $type = 0) { /* {{{ */
1736        $db = $this->_dms->getDB();
1737
1738        /* Verify that user / group exists. */
1739        /** @var SeedDMS_Core_Group|SeedDMS_Core_User $obj */
1740        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1741        if (!is_object($obj)) {
1742            return -1;
1743        }
1744
1745        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1746
1747        /* Verify that the requesting user has permission to add the target to
1748         * the notification system.
1749         */
1750        /*
1751         * The calling application should enforce the policy on who is allowed
1752         * to add someone to the notification system. If is shall remain here
1753         * the currently logged in user should be passed to this function
1754         *
1755        GLOBAL $user;
1756        if ($user->isGuest()) {
1757            return -2;
1758        }
1759        if (!$user->isAdmin()) {
1760            if ($isUser) {
1761                if ($user->getID() != $obj->getID()) {
1762                    return -2;
1763                }
1764            }
1765            else {
1766                if (!$obj->isMember($user)) {
1767                    return -2;
1768                }
1769            }
1770        }
1771         */
1772
1773        /* Check to see if the target is in the database. */
1774        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
1775            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
1776            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
1777        $resArr = $db->getResultArray($queryStr);
1778        if (is_bool($resArr)) {
1779            return -4;
1780        }
1781        if (count($resArr)==0) {
1782            return -3;
1783        }
1784
1785        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1786        /* If type is given then delete only those notifications */
1787        if ($type)
1788            $queryStr .= " AND `type` = ".(int) $type;
1789        if (!$db->getResult($queryStr))
1790            return -4;
1791
1792        unset($this->_notifyList);
1793        return 0;
1794    } /* }}} */
1795
1796    /**
1797     * Add content to a document
1798     *
1799     * Each document may have any number of content elements attached to it.
1800     * Each content element has a version number. Newer versions (greater
1801     * version number) replace older versions.
1802     *
1803     * @param string $comment comment
1804     * @param object $user user who shall be the owner of this content
1805     * @param string $tmpFile file containing the actuall content
1806     * @param string $orgFileName original file name
1807     * @param string $fileType
1808     * @param string $mimeType MimeType of the content
1809     * @param array $reviewers list of reviewers
1810     * @param array $approvers list of approvers
1811     * @param integer $version version number of content or 0 if next higher version shall be used.
1812     * @param array $attributes list of version attributes. The element key
1813     *        must be the id of the attribute definition.
1814     * @param object $workflow
1815     * @return bool|SeedDMS_Core_AddContentResultSet
1816     */
1817    public function addContent($comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers = array(), $approvers = array(), $version = 0, $attributes = array(), $workflow = null) { /* {{{ */
1818        $db = $this->_dms->getDB();
1819
1820        // the doc path is id/version.filetype
1821        $dir = $this->getDir();
1822
1823        /* The version field in table tblDocumentContent used to be auto
1824         * increment but that requires the field to be primary as well if
1825         * innodb is used. That's why the version is now determined here.
1826         */
1827        if ((int)$version<1) {
1828            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
1829            $resArr = $db->getResultArray($queryStr);
1830            if (is_bool($resArr) && !$resArr)
1831                return false;
1832
1833            $version = $resArr[0]['m']+1;
1834        }
1835
1836        if ($fileType == '.')
1837            $fileType = '';
1838        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
1839        $checksum = SeedDMS_Core_File::checksum($tmpFile);
1840
1841        $db->startTransaction();
1842        $queryStr = "INSERT INTO `tblDocumentContent` (`document`, `version`, `comment`, `date`, `createdBy`, `dir`, `orgFileName`, `fileType`, `mimeType`, `fileSize`, `checksum`) VALUES ".
1843            "(".$this->_id.", ".(int)$version.",".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$user->getID().", ".$db->qstr($dir).", ".$db->qstr($orgFileName).", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$filesize.", ".$db->qstr($checksum).")";
1844        if (!$db->getResult($queryStr)) {
1845            $db->rollbackTransaction();
1846            return false;
1847        }
1848
1849        $contentID = $db->getInsertID('tblDocumentContent');
1850        $content = $this->_dms->getDocumentContent($contentID);
1851
1852        if ($storage = $this->_dms->getStorage()) {
1853            if (!$storage->saveContent($this, $content, $tmpFile)) {
1854                $db->rollbackTransaction();
1855                return false;
1856            }
1857        } else {
1858        // copy file
1859        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) {
1860            $db->rollbackTransaction();
1861            return false;
1862        }
1863        if ($this->_dms->forceRename)
1864            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1865        elseif ($this->_dms->forceLink)
1866            $err = SeedDMS_Core_File::linkFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1867        else
1868            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
1869        if (!$err) {
1870            $db->rollbackTransaction();
1871            return false;
1872        }
1873        }
1874
1875        $this->_content = null;
1876        $this->_latestContent = null;
1877        $docResultSet = new SeedDMS_Core_AddContentResultSet($content);
1878        $docResultSet->setDMS($this->_dms);
1879
1880        if ($attributes) {
1881            foreach ($attributes as $attrdefid => $attribute) {
1882                /* $attribute can be a string or an array */
1883                if ($attribute) {
1884                    if ($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
1885                        if (!$content->setAttributeValue($attrdef, $attribute)) {
1886                            $this->_removeContent($content);
1887                            $db->rollbackTransaction();
1888                            return false;
1889                        }
1890                    } else {
1891                        $this->_removeContent($content);
1892                        $db->rollbackTransaction();
1893                        return false;
1894                    }
1895                }
1896            }
1897        }
1898
1899        $queryStr = "INSERT INTO `tblDocumentStatus` (`documentID`, `version`) ".
1900            "VALUES (". $this->_id .", ". (int) $version .")";
1901        if (!$db->getResult($queryStr)) {
1902            $this->_removeContent($content);
1903            $db->rollbackTransaction();
1904            return false;
1905        }
1906
1907        $statusID = $db->getInsertID('tblDocumentStatus', 'statusID');
1908
1909        if ($workflow)
1910            $content->setWorkflow($workflow, $user);
1911
1912        // Add reviewers into the database. Reviewers must review the document
1913        // and submit comments, if appropriate. Reviewers can also recommend that
1914        // a document be rejected.
1915        $pendingReview = false;
1916        /** @noinspection PhpUnusedLocalVariableInspection */
1917        foreach (array("i", "g") as $i) {
1918            if (isset($reviewers[$i])) {
1919                foreach ($reviewers[$i] as $reviewerID) {
1920                    $reviewer = ($i == "i" ?$this->_dms->getUser($reviewerID) : $this->_dms->getGroup($reviewerID));
1921                    $res = ($i == "i" ? $docResultSet->getContent()->addIndReviewer($reviewer, $user, true) : $docResultSet->getContent()->addGrpReviewer($reviewer, $user, true));
1922                    $docResultSet->addReviewer($reviewer, $i, $res);
1923                    // res is the id of the record in the database
1924                    if ($res > 0 || $res == -3) {
1925                        $pendingReview = true;
1926                    }
1927                }
1928            }
1929        }
1930        // Add approvers to the database. Approvers must also review the document
1931        // and make a recommendation on its release as an approved version.
1932        $pendingApproval = false;
1933        /** @noinspection PhpUnusedLocalVariableInspection */
1934        foreach (array("i", "g") as $i) {
1935            if (isset($approvers[$i])) {
1936                foreach ($approvers[$i] as $approverID) {
1937                    $approver = ($i == "i" ? $this->_dms->getUser($approverID) : $this->_dms->getGroup($approverID));
1938                    $res = ($i == "i" ? $docResultSet->getContent()->addIndApprover($approver, $user, true) : $docResultSet->getContent()->addGrpApprover($approver, $user, !$pendingReview));
1939                    $docResultSet->addApprover($approver, $i, $res);
1940                    // res is the id of the record in the database
1941                    if ($res > 0 || $res == -3) {
1942                        $pendingApproval = true;
1943                    }
1944                }
1945            }
1946        }
1947
1948        // If there are no reviewers or approvers, the document is automatically
1949        // promoted to the released state.
1950        if ($pendingReview) {
1951            $status = S_DRAFT_REV;
1952            $comment = "";
1953        }
1954        elseif ($pendingApproval) {
1955            $status = S_DRAFT_APP;
1956            $comment = "";
1957        }
1958        elseif ($workflow) {
1959            $status = S_IN_WORKFLOW;
1960            $comment = ", workflow: ".$workflow->getName();
1961        } else {
1962            $status = S_RELEASED;
1963            $comment = "";
1964        }
1965        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
1966            "VALUES ('". $statusID ."', '". $status."', 'New document content submitted". $comment ."', ".$db->getCurrentDatetime().", '". $user->getID() ."')";
1967        if (!$db->getResult($queryStr)) {
1968            $db->rollbackTransaction();
1969            return false;
1970        }
1971
1972        /** @noinspection PhpMethodParametersCountMismatchInspection */
1973        $docResultSet->setStatus($status);
1974
1975        $db->commitTransaction();
1976        return $docResultSet;
1977    } /* }}} */
1978
1979    /**
1980     * Replace a version of a document
1981     *
1982     * Each document may have any number of content elements attached to it.
1983     * This method replaces the file content of a given version.
1984     * Using this function is highly discourage, because it undermines the
1985     * idea of keeping all versions of a document as originally saved.
1986     * Content will only be replaced if the mimetype, filetype, user and
1987     * original filename are identical to the version being updated.
1988     *
1989     * This method was introduced for the webdav server because any saving
1990     * of a document created a new version.
1991     *
1992     * @param object $user user who shall be the owner of this content
1993     * @param string $tmpFile file containing the actuall content
1994     * @param string $orgFileName original file name
1995     * @param string $fileType
1996     * @param string $mimeType MimeType of the content
1997     * @param integer $version version number of content or 0 if latest version shall be replaced.
1998     * @return bool/array false in case of an error or a result set
1999     */
2000    public function replaceContent($version, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $allowoverride = []) { /* {{{ */
2001        $db = $this->_dms->getDB();
2002
2003        // the doc path is id/version.filetype
2004        $dir = $this->getDir();
2005
2006        /* If $version < 1 than replace the content of the latest version.
2007         */
2008        if ((int) $version<1) {
2009            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2010            $resArr = $db->getResultArray($queryStr);
2011            if (is_bool($resArr) && !$resArr)
2012                return false;
2013
2014            $version = $resArr[0]['m'];
2015        }
2016
2017        $content = $this->getContentByVersion($version);
2018        if (!$content)
2019            return false;
2020
2021        if ($fileType == '.')
2022            $fileType = '';
2023
2024        $sql = [];
2025        /* Check if $user, $orgFileName, $fileType and $mimeType are the same */
2026        if ($user->getID() != $content->getUser()->getID()) {
2027            if (!empty($allowoverride['user']))
2028                $sql[] = "`createdBy`=".$user->getID();
2029            else
2030                return false;
2031        }
2032        if ($orgFileName != $content->getOriginalFileName()) {
2033            if (!empty($allowoverride['orgfilename']))
2034                $sql[] = "`orgFileName`=".$db->qstr($orgFileName);
2035            else
2036                return false;
2037        }
2038        if ($fileType != $content->getFileType()) {
2039            if (!empty($allowoverride['filetype']))
2040                $sql[] = "`fileType`=".$db->qstr($fileType);
2041            else
2042                return false;
2043        }
2044        if ($mimeType != $content->getMimeType()) {
2045            if (!empty($allowoverride['mimetype']))
2046                $sql[] = "`mimeType`=".$db->qstr($mimeType);
2047            else
2048                return false;
2049        }
2050
2051        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2052        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2053
2054        $db->startTransaction();
2055        $sql[] = "`date`=".$db->getCurrentTimestamp();
2056        $sql[] = "`fileSize`=".$filesize;
2057        $sql[] = "`checksum`=".$db->qstr($checksum);
2058        $queryStr = "UPDATE `tblDocumentContent` set ".implode(", ", $sql)." WHERE `id`=".$content->getID();
2059        if (!$db->getResult($queryStr)) {
2060            $db->rollbackTransaction();
2061            return false;
2062        }
2063
2064        if ($storage = $this->_dms->getStorage()) {
2065            if (!$storage->replaceContent($this, $content, $tmpFile)) {
2066                $db->rollbackTransaction();
2067                return false;
2068            }
2069        } else {
2070        // copy file
2071            if (!SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType)) {
2072                $db->rollbackTransaction();
2073                return false;
2074            }
2075        }
2076
2077        $this->_content = null;
2078        $this->_latestContent = null;
2079        $db->commitTransaction();
2080
2081        return true;
2082    } /* }}} */
2083
2084    /**
2085     * Return all content elements of a document
2086     *
2087     * This method returns an array of content elements ordered by version.
2088     * Version which are not accessible because of its status, will be filtered
2089     * out. Access rights based on the document status are calculated for the
2090     * currently logged in user.
2091     *
2092     * @return bool|SeedDMS_Core_DocumentContent[]
2093     */
2094    public function getContent() { /* {{{ */
2095        $db = $this->_dms->getDB();
2096
2097        if (!isset($this->_content)) {
2098            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version`";
2099            $resArr = $db->getResultArray($queryStr);
2100            if (is_bool($resArr) && !$resArr)
2101                return false;
2102
2103            $this->_content = array();
2104            $classname = $this->_dms->getClassname('documentcontent');
2105            $user = $this->_dms->getLoggedInUser();
2106            foreach ($resArr as $row) {
2107                /** @var SeedDMS_Core_DocumentContent $content */
2108                $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum']);
2109                /* TODO: Better use content id as key in $this->_content. This
2110                 * would allow to remove a single content object in removeContent().
2111                 * Currently removeContent() must clear $this->_content completely
2112                 */
2113                if ($user) {
2114                    if ($content->getAccessMode($user) >= M_READ)
2115                        array_push($this->_content, $content);
2116                } else {
2117                    array_push($this->_content, $content);
2118                }
2119            }
2120        }
2121
2122        return $this->_content;
2123    } /* }}} */
2124
2125    /**
2126     * Return the content element of a document with a given version number
2127     *
2128     * This method will check if the version is accessible and return false
2129     * if not. Access rights based on the document status are calculated for the
2130     * currently logged in user.
2131     *
2132     * @param integer $version version number of content element
2133     * @return SeedDMS_Core_DocumentContent|null|boolean object of class
2134     * {@see SeedDMS_Core_DocumentContent}, null if not content was found,
2135     * false in case of an error
2136     */
2137    public function getContentByVersion($version) { /* {{{ */
2138        if (!is_numeric($version)) return false;
2139
2140        if (isset($this->_content)) {
2141            foreach ($this->_content as $revision) {
2142                if ($revision->getVersion() == $version)
2143                    return $revision;
2144            }
2145            return null;
2146        }
2147
2148        $db = $this->_dms->getDB();
2149        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." AND `version` = " . (int) $version;
2150        $resArr = $db->getResultArray($queryStr);
2151        if (is_bool($resArr) && !$resArr)
2152            return false;
2153        if (count($resArr) != 1)
2154            return null;
2155
2156        $resArr = $resArr[0];
2157        $classname = $this->_dms->getClassname('documentcontent');
2158        /** @var SeedDMS_Core_DocumentContent $content */
2159        if ($content = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'])) {
2160            $user = $this->_dms->getLoggedInUser();
2161            /* A user with write access on the document may always see the version */
2162            if ($user && $content->getAccessMode($user) == M_NONE)
2163                return null;
2164            else
2165                return $content;
2166        } else {
2167            return false;
2168        }
2169    } /* }}} */
2170
2171    /**
2172     * Check if a given version is the latest version of the document
2173     *
2174     * @param integer $version version number of content element
2175     * @return SeedDMS_Core_DocumentContent|boolean object of class {@see SeedDMS_Core_DocumentContent}
2176     * or false
2177     */
2178    public function isLatestContent($version) { /* {{{ */
2179        return $this->getLatestContent()->getVersion() == $version;
2180    } /* }}} */
2181
2182    /**
2183     * @return bool|null|SeedDMS_Core_DocumentContent
2184     */
2185    private function __getLatestContent() { /* {{{ */
2186        if (!$this->_latestContent) {
2187            $db = $this->_dms->getDB();
2188            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC LIMIT 1";
2189            $resArr = $db->getResultArray($queryStr);
2190            if (is_bool($resArr) && !$resArr)
2191                return false;
2192            if (count($resArr) != 1)
2193                return false;
2194
2195            $resArr = $resArr[0];
2196            $classname = $this->_dms->getClassname('documentcontent');
2197            $this->_latestContent = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum']);
2198        }
2199        return $this->_latestContent;
2200    } /* }}} */
2201
2202    /**
2203     * Get the latest version of document
2204     *
2205     * This method returns the latest accessible version of a document.
2206     * If content access has been restricted by setting
2207     * {@see SeedDMS_Core_DMS::noReadForStatus} the function will go
2208     * backwards in history until an accessible version is found. If none
2209     * is found null will be returned.
2210     * Access rights based on the document status are calculated for the
2211     * currently logged in user.
2212     *
2213     * @return bool|SeedDMS_Core_DocumentContent object of class {@see SeedDMS_Core_DocumentContent}
2214     */
2215    public function getLatestContent() { /* {{{ */
2216        if (!$this->_latestContent) {
2217            $db = $this->_dms->getDB();
2218            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC";
2219            $resArr = $db->getResultArray($queryStr);
2220            if (is_bool($resArr) && !$resArr)
2221                return false;
2222
2223            $classname = $this->_dms->getClassname('documentcontent');
2224            $user = $this->_dms->getLoggedInUser();
2225            foreach ($resArr as $row) {
2226                if (!$this->_latestContent) {
2227                    /** @var SeedDMS_Core_DocumentContent $content */
2228                    $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum']);
2229                    if ($user) {
2230                        /* If the user may even write the document, then also allow to see all content.
2231                         * This is needed because the user could upload a new version
2232                         */
2233                        if ($content->getAccessMode($user) >= M_READ) {
2234                            $this->_latestContent = $content;
2235                        }
2236                    } else {
2237                        $this->_latestContent = $content;
2238                    }
2239                }
2240            }
2241        }
2242
2243        return $this->_latestContent;
2244    } /* }}} */
2245
2246    /**
2247     * Remove version of document
2248     *
2249     * @param SeedDMS_Core_DocumentContent $version version number of content
2250     * @return boolean true if successful, otherwise false
2251     */
2252    private function _removeContent($version) { /* {{{ */
2253        $db = $this->_dms->getDB();
2254
2255        $db->startTransaction();
2256
2257        $status = $version->getStatus();
2258        $stID = $status["statusID"];
2259
2260        $queryStr = "DELETE FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2261        if (!$db->getResult($queryStr)) {
2262            $db->rollbackTransaction();
2263            return false;
2264        }
2265
2266        $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $version->getId();
2267        if (!$db->getResult($queryStr)) {
2268            $db->rollbackTransaction();
2269            return false;
2270        }
2271
2272        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID` = '".$stID."'";
2273        if (!$db->getResult($queryStr)) {
2274            $db->rollbackTransaction();
2275            return false;
2276        }
2277
2278        $queryStr = "DELETE FROM `tblDocumentStatus` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2279        if (!$db->getResult($queryStr)) {
2280            $db->rollbackTransaction();
2281            return false;
2282        }
2283
2284        $status = $version->getReviewStatus();
2285        $stList = "";
2286        foreach ($status as $st) {
2287            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["reviewID"]."'";
2288            $queryStr = "SELECT * FROM `tblDocumentReviewLog` WHERE `reviewID` = " . $st['reviewID'];
2289            $resArr = $db->getResultArray($queryStr);
2290            if ((is_bool($resArr) && !$resArr)) {
2291                $db->rollbackTransaction();
2292                return false;
2293            }
2294            foreach ($resArr as $res) {
2295                if ($storage = $this->_dms->getStorage()) {
2296                    $storage->deleteReview($this, $res['reviewLogID']);
2297                } else {
2298                $file = $this->_dms->contentDir . $this->getDir().'r'.$res['reviewLogID'];
2299                if (SeedDMS_Core_File::file_exists($file))
2300                    SeedDMS_Core_File::removeFile($file);
2301                }
2302            }
2303        }
2304
2305        if (strlen($stList)>0) {
2306            $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `tblDocumentReviewLog`.`reviewID` IN (".$stList.")";
2307            if (!$db->getResult($queryStr)) {
2308                $db->rollbackTransaction();
2309                return false;
2310            }
2311        }
2312        $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2313        if (!$db->getResult($queryStr)) {
2314            $db->rollbackTransaction();
2315            return false;
2316        }
2317        $status = $version->getApprovalStatus();
2318        $stList = "";
2319        foreach ($status as $st) {
2320            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["approveID"]."'";
2321            $queryStr = "SELECT * FROM `tblDocumentApproveLog` WHERE `approveID` = " . $st['approveID'];
2322            $resArr = $db->getResultArray($queryStr);
2323            if ((is_bool($resArr) && !$resArr)) {
2324                $db->rollbackTransaction();
2325                return false;
2326            }
2327            foreach ($resArr as $res) {
2328                if ($storage = $this->_dms->getStorage()) {
2329                    $storage->deleteApproval($this, $res['approveLogID']);
2330                } else {
2331                $file = $this->_dms->contentDir . $this->getDir().'a'.$res['approveLogID'];
2332                if (SeedDMS_Core_File::file_exists($file))
2333                    SeedDMS_Core_File::removeFile($file);
2334                }
2335            }
2336        }
2337
2338        if (strlen($stList)>0) {
2339            $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `tblDocumentApproveLog`.`approveID` IN (".$stList.")";
2340            if (!$db->getResult($queryStr)) {
2341                $db->rollbackTransaction();
2342                return false;
2343            }
2344        }
2345        $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2346        if (!$db->getResult($queryStr)) {
2347            $db->rollbackTransaction();
2348            return false;
2349        }
2350
2351        $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2352        if (!$db->getResult($queryStr)) {
2353            $db->rollbackTransaction();
2354            return false;
2355        }
2356
2357        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2358        if (!$db->getResult($queryStr)) {
2359            $db->rollbackTransaction();
2360            return false;
2361        }
2362
2363        // remove only those document files attached to version
2364        $res = $this->getDocumentFiles($version->getVersion(), false);
2365        if (is_bool($res) && !$res) {
2366            $db->rollbackTransaction();
2367            return false;
2368        }
2369
2370        foreach ($res as $documentfile)
2371            if (!$this->removeDocumentFile($documentfile->getId())) {
2372                $db->rollbackTransaction();
2373                return false;
2374            }
2375
2376        if ($storage = $this->_dms->getStorage()) {
2377            if (!$storage->deleteContent($this, $version)) {
2378                $db->rollbackTransaction();
2379                return false;
2380            }
2381        } else {
2382        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir.$version->getPath()))
2383            if (!SeedDMS_Core_File::removeFile($this->_dms->contentDir.$version->getPath())) {
2384                $db->rollbackTransaction();
2385                return false;
2386            }
2387        }
2388
2389        $db->commitTransaction();
2390        return true;
2391    } /* }}} */
2392
2393    /**
2394     * Call callback onPreRemoveDocument before deleting content
2395     *
2396     * @param SeedDMS_Core_DocumentContent $version version number of content
2397     * @return bool|mixed
2398     */
2399    public function removeContent($version) { /* {{{ */
2400        $this->_dms->lasterror = '';
2401        $db = $this->_dms->getDB();
2402
2403        /* Make sure the version exists */
2404        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2405        $resArr = $db->getResultArray($queryStr);
2406        if (is_bool($resArr) && !$resArr)
2407            return false;
2408        if (count($resArr)==0)
2409            return false;
2410
2411        /* Make sure this is not the last version */
2412        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID();
2413        $resArr = $db->getResultArray($queryStr);
2414        if (is_bool($resArr) && !$resArr)
2415            return false;
2416        if (count($resArr)==1)
2417            return false;
2418
2419        /* Check if 'onPreRemoveDocument' callback is set */
2420        if (isset($this->_dms->callbacks['onPreRemoveContent'])) {
2421            foreach ($this->_dms->callbacks['onPreRemoveContent'] as $callback) {
2422                $ret = call_user_func($callback[0], $callback[1], $this, $version);
2423                if (is_bool($ret))
2424                    return $ret;
2425            }
2426        }
2427
2428        if (false === ($ret = self::_removeContent($version))) {
2429            return false;
2430        }
2431
2432        /* Invalidate the content list and the latest content of this document,
2433         * otherwise getContent() and getLatestContent()
2434         * will still return the content just deleted.
2435         */
2436        $this->_latestContent = null;
2437        $this->_content = null;
2438
2439        /* Check if 'onPostRemoveDocument' callback is set */
2440        if (isset($this->_dms->callbacks['onPostRemoveContent'])) {
2441            foreach ($this->_dms->callbacks['onPostRemoveContent'] as $callback) {
2442                if (!call_user_func($callback[0], $callback[1], $version)) {
2443                }
2444            }
2445        }
2446
2447        return $ret;
2448    } /* }}} */
2449
2450    /**
2451     * Return a certain document link
2452     *
2453     * @param integer $linkID id of link
2454     * @return SeedDMS_Core_DocumentLink|bool of SeedDMS_Core_DocumentLink or false in case of
2455     *         an error.
2456     */
2457    public function getDocumentLink($linkID) { /* {{{ */
2458        $db = $this->_dms->getDB();
2459
2460        if (!is_numeric($linkID)) return false;
2461
2462        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2463        $resArr = $db->getResultArray($queryStr);
2464        if (is_bool($resArr) && !$resArr)
2465            return false;
2466        if (count($resArr)==0)
2467            return null;
2468
2469        $resArr = $resArr[0];
2470        $document = $this->_dms->getDocument($resArr["document"]);
2471        $target = $this->_dms->getDocument($resArr["target"]);
2472        if ($document && $target) {
2473            $link = new SeedDMS_Core_DocumentLink($resArr["id"], $document, $target, $resArr["userID"], $resArr["public"]);
2474            $user = $this->_dms->getLoggedInUser();
2475            if ($link->getAccessMode($user, $document, $target) >= M_READ)
2476                return $link;
2477        }
2478        return null;
2479    } /* }}} */
2480
2481    /**
2482     * Return all document links
2483     *
2484     * The list may contain all links to other documents, even those which
2485     * may not be visible by certain users, unless you pass appropriate
2486     * parameters to filter out public links and those created by
2487     * the given user. The two parameters are or'ed. If $publiconly
2488     * is set the method will return all public links disregarding the
2489     * user. If $publiconly is not set but a user is set, the method
2490     * will return all links of that user (public and none public).
2491     * Setting a user and $publiconly to true will *not* return the
2492     * public links of that user but all links which are public or
2493     * owned by that user.
2494     *
2495     * The application must call
2496     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2497     * those links pointing to a document not accessible by a given user.
2498     *
2499     * @param boolean           $publiconly return all publically visible links
2500     * @param SeedDMS_Core_User $user       return also private links of this user
2501     *
2502     * @return array list of objects of class {@see SeedDMS_Core_DocumentLink}
2503     */
2504    public function getDocumentLinks($publiconly = false, $user = null) { /* {{{ */
2505        if (!isset($this->_documentLinks)) {
2506            $db = $this->_dms->getDB();
2507
2508            $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id;
2509            $tmp = array();
2510            if ($publiconly)
2511                $tmp[] = "`public`=1";
2512            if ($user)
2513                $tmp[] = "`userID`=".$user->getID();
2514            if ($tmp) {
2515                $queryStr .= " AND (".implode(" OR ", $tmp).")";
2516            }
2517
2518            $resArr = $db->getResultArray($queryStr);
2519            if (is_bool($resArr) && !$resArr)
2520                return false;
2521            $this->_documentLinks = array();
2522
2523            $user = $this->_dms->getLoggedInUser();
2524            foreach ($resArr as $row) {
2525                $target = $this->_dms->getDocument($row["target"]);
2526                if ($target) {
2527                    $link = new SeedDMS_Core_DocumentLink($row["id"], $this, $target, $row["userID"], $row["public"]);
2528                    if ($link->getAccessMode($user, $this, $target) >= M_READ)
2529                        array_push($this->_documentLinks, $link);
2530                }
2531            }
2532        }
2533        return $this->_documentLinks;
2534    } /* }}} */
2535
2536    /**
2537     * Return all document having a link on this document
2538     *
2539     * The list contains all documents which have a link to the current
2540     * document. The list contains even those documents which
2541     * may not be accessible by the user, unless you pass appropriate
2542     * parameters to filter out public links and those created by
2543     * the given user.
2544     * This method is basically the reverse of
2545     * {@see SeedDMS_Core_Document::getDocumentLinks()}
2546     *
2547     * The application must call
2548     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2549     * those links pointing to a document not accessible by a given user.
2550     *
2551     * @param boolean           $publiconly return all publically visible links
2552     * @param SeedDMS_Core_User $user       return also private links of this user
2553     *
2554     * @return array list of objects of class SeedDMS_Core_DocumentLink
2555     */
2556    public function getReverseDocumentLinks($publiconly = false, $user = null) { /* {{{ */
2557        $db = $this->_dms->getDB();
2558
2559        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `target` = " . $this->_id;
2560        $tmp = array();
2561        if ($publiconly)
2562            $tmp[] = "`public`=1";
2563        if ($user)
2564            $tmp[] = "`userID`=".$user->getID();
2565        if ($tmp) {
2566            $queryStr .= " AND (".implode(" OR ", $tmp).")";
2567        }
2568
2569        $resArr = $db->getResultArray($queryStr);
2570        if (is_bool($resArr) && !$resArr)
2571            return false;
2572
2573        $links = array();
2574        foreach ($resArr as $row) {
2575            $document = $this->_dms->getDocument($row["document"]);
2576            $link = new SeedDMS_Core_DocumentLink($row["id"], $document, $this, $row["userID"], $row["public"]);
2577            if ($link->getAccessMode($user, $document, $this) >= M_READ)
2578                array_push($links, $link);
2579        }
2580
2581        return $links;
2582    } /* }}} */
2583
2584    /**
2585     * Add a link to a target document
2586     *
2587     * @param int $targetID Id of target document
2588     * @param int $userID Id of user adding the link
2589     * @param boolean true if link is public
2590     * @return SeedDMS_Core_DocumentLink|boolean
2591     */
2592    public function addDocumentLink($targetID, $userID, $public) { /* {{{ */
2593        $db = $this->_dms->getDB();
2594
2595        $public = ($public) ? 1 : 0;
2596
2597        if (!is_numeric($targetID) || $targetID < 1)
2598            return false;
2599
2600        if ($targetID == $this->_id)
2601            return false;
2602
2603        if (!is_numeric($userID) || $userID < 1)
2604            return false;
2605
2606        if (!($target = $this->_dms->getDocument($targetID)))
2607            return false;
2608
2609        if (!($user = $this->_dms->getUser($userID)))
2610            return false;
2611
2612        $queryStr = "INSERT INTO `tblDocumentLinks` (`document`, `target`, `userID`, `public`) VALUES (".$this->_id.", ".(int)$targetID.", ".(int)$userID.", ".$public.")";
2613        if (!$db->getResult($queryStr))
2614            return false;
2615
2616        unset($this->_documentLinks);
2617
2618        $id = $db->getInsertID('tblDocumentLinks');
2619        $link = new SeedDMS_Core_DocumentLink($id, $this, $target, $user->getId(), $public);
2620        return $link;
2621    } /* }}} */
2622
2623    public function removeDocumentLink($linkID) { /* {{{ */
2624        $db = $this->_dms->getDB();
2625
2626        if (!is_numeric($linkID) || $linkID < 1)
2627            return false;
2628
2629        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2630        if (!$db->getResult($queryStr)) return false;
2631        unset($this->_documentLinks);
2632        return true;
2633    } /* }}} */
2634
2635    /**
2636     * Get attached file by its id
2637     *
2638     * @return object instance of SeedDMS_Core_DocumentFile, null if file is not
2639     * accessible, false in case of an sql error
2640     */
2641    public function getDocumentFile($ID) { /* {{{ */
2642        $db = $this->_dms->getDB();
2643
2644        if (!is_numeric($ID)) return false;
2645
2646        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $ID;
2647        $resArr = $db->getResultArray($queryStr);
2648        if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false;
2649
2650        $resArr = $resArr[0];
2651        $classname = $this->_dms->getClassname('documentfile');
2652        $file = new $classname($resArr["id"], $this, $resArr["userID"], $resArr["comment"], $resArr["date"], $resArr["dir"], $resArr["fileType"], $resArr["mimeType"], $resArr["orgFileName"], $resArr["name"], $resArr["version"], $resArr["public"]);
2653        $user = $this->_dms->getLoggedInUser();
2654        if ($file->getAccessMode($user) >= M_READ)
2655            return $file;
2656        return null;
2657    } /* }}} */
2658
2659    /**
2660     * Get list of files attached to document
2661     *
2662     * @param integer $version      get only attachments for this version
2663     * @param boolean $incnoversion include attachments without a version
2664     *
2665     * @return array list of files, false in case of an sql error
2666     */
2667    public function getDocumentFiles($version = 0, $incnoversion = true) { /* {{{ */
2668        /* use a smarter caching because removing a document will call this function
2669         * for each version and the document itself.
2670         */
2671        $hash = substr(md5($version.$incnoversion), 0, 4);
2672        if (!isset($this->_documentFiles[$hash])) {
2673            $db = $this->_dms->getDB();
2674
2675            $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
2676            if ($version) {
2677                if ($incnoversion)
2678                    $queryStr .= " AND (`version`=0 OR `version`=".(int) $version.")";
2679                else
2680                    $queryStr .= " AND (`version`=".(int) $version.")";
2681            }
2682            $queryStr .= " ORDER BY ";
2683            if ($version) {
2684                $queryStr .= "`version` DESC,";
2685            }
2686            $queryStr .= "`date` DESC";
2687            $resArr = $db->getResultArray($queryStr);
2688            if (is_bool($resArr) && !$resArr) return false;
2689
2690            $this->_documentFiles = array($hash => array());
2691
2692            $user = $this->_dms->getLoggedInUser();
2693            $classname = $this->_dms->getClassname('documentfile');
2694            foreach ($resArr as $row) {
2695                $file = new $classname($row["id"], $this, $row["userID"], $row["comment"], $row["date"], $row["dir"], $row["fileType"], $row["mimeType"], $row["orgFileName"], $row["name"], $row["version"], $row["public"]);
2696                if ($file->getAccessMode($user) >= M_READ)
2697                    array_push($this->_documentFiles[$hash], $file);
2698            }
2699        }
2700        return $this->_documentFiles[$hash];
2701    } /* }}} */
2702
2703    /**
2704     * Add an attachment to the document
2705     *
2706     */
2707    public function addDocumentFile($name, $comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $version = 0, $public = 1) { /* {{{ */
2708        $db = $this->_dms->getDB();
2709
2710        $dir = $this->getDir();
2711
2712        $db->startTransaction();
2713        $queryStr = "INSERT INTO `tblDocumentFiles` (`comment`, `date`, `dir`, `document`, `fileType`, `mimeType`, `orgFileName`, `userID`, `name`, `version`, `public`) VALUES ".
2714            "(".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$db->qstr($dir).", ".$this->_id.", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$db->qstr($orgFileName).",".$user->getID().",".$db->qstr($name).", ".((int) $version).", ".($public ? 1 : 0).")";
2715        if (!$db->getResult($queryStr)) {
2716            $db->rollbackTransaction();
2717            return false;
2718        }
2719
2720        $id = $db->getInsertID('tblDocumentFiles');
2721
2722        $file = $this->getDocumentFile($id);
2723        if (is_bool($file) && !$file) {
2724            $db->rollbackTransaction();
2725            return false;
2726        }
2727
2728        if ($storage = $this->_dms->getStorage()) {
2729            $err = $storage->saveAttachment($this, $file, $tmpFile);
2730        } else {
2731        // copy file
2732        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) return false;
2733        if ($this->_dms->forceRename)
2734            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $file->getPath());
2735        else
2736            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $file->getPath());
2737        }
2738        if (!$err) {
2739            $db->rollbackTransaction();
2740            return false;
2741        }
2742
2743        $db->commitTransaction();
2744        unset($this->_documentFiles);
2745        return $file;
2746    } /* }}} */
2747
2748    public function removeDocumentFile($ID) { /* {{{ */
2749        $db = $this->_dms->getDB();
2750
2751        if (!is_numeric($ID) || $ID < 1)
2752            return false;
2753
2754        $file = $this->getDocumentFile($ID);
2755        if (is_bool($file) && !$file) return false;
2756
2757        $db->startTransaction();
2758        /* First delete the database record, because that can be undone
2759         * if deletion of the file fails.
2760         */
2761        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->getID() . " AND `id` = " . (int) $ID;
2762        if (!$db->getResult($queryStr)) {
2763            $db->rollbackTransaction();
2764            return false;
2765        }
2766
2767        if ($storage = $this->_dms->getStorage()) {
2768            if (!$storage->deleteAttachment($this, $file)) {
2769                $db->rollbackTransaction();
2770                return false;
2771            }
2772        } else {
2773        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir . $file->getPath())) {
2774            if (!SeedDMS_Core_File::removeFile($this->_dms->contentDir . $file->getPath())) {
2775                $db->rollbackTransaction();
2776                return false;
2777            }
2778        }
2779        }
2780
2781        $db->commitTransaction();
2782        unset($this->_documentFiles);
2783
2784        return true;
2785    } /* }}} */
2786
2787    /**
2788     * Remove a document completly
2789     *
2790     * This methods calls the callback 'onPreRemoveDocument' before removing
2791     * the document. The current document will be passed as the second
2792     * parameter to the callback function. After successful deletion the
2793     * 'onPostRemoveDocument' callback will be used. The current document id
2794     * will be passed as the second parameter. If onPreRemoveDocument fails
2795     * the whole function will fail and the document will not be deleted.
2796     * The return value of 'onPostRemoveDocument' will be disregarded.
2797     *
2798     * @return boolean true on success, otherwise false
2799     */
2800    public function remove() { /* {{{ */
2801        $db = $this->_dms->getDB();
2802        $this->_dms->lasterror = '';
2803
2804        /* Check if 'onPreRemoveDocument' callback is set */
2805        if (isset($this->_dms->callbacks['onPreRemoveDocument'])) {
2806            foreach ($this->_dms->callbacks['onPreRemoveDocument'] as $callback) {
2807                $ret = call_user_func($callback[0], $callback[1], $this);
2808                if (is_bool($ret))
2809                    return $ret;
2810            }
2811        }
2812
2813        $res = $this->getContent();
2814        if (is_bool($res) && !$res) return false;
2815
2816        $db->startTransaction();
2817
2818        // remove content of document
2819        foreach ($this->_content as $version) {
2820            if (!$this->_removeContent($version)) {
2821                $db->rollbackTransaction();
2822                return false;
2823            }
2824        }
2825
2826        // remove all document files
2827        $res = $this->getDocumentFiles();
2828        if (is_bool($res) && !$res) {
2829            $db->rollbackTransaction();
2830            return false;
2831        }
2832
2833        foreach ($res as $documentfile)
2834            if (!$this->removeDocumentFile($documentfile->getId())) {
2835                $db->rollbackTransaction();
2836                return false;
2837            }
2838
2839        // TODO: versioning file?
2840
2841        if ($storage = $this->_dms->getStorage()) {
2842            if (!$storage->deleteDocDir($this)) {
2843                $db->rollbackTransaction();
2844                return false;
2845            }
2846        } else {
2847        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir . $this->getDir()))
2848            if (!SeedDMS_Core_File::removeDir($this->_dms->contentDir . $this->getDir())) {
2849                $db->rollbackTransaction();
2850                return false;
2851            }
2852        }
2853
2854        $queryStr = "DELETE FROM `tblDocuments` WHERE `id` = " . $this->_id;
2855        if (!$db->getResult($queryStr)) {
2856            $db->rollbackTransaction();
2857            return false;
2858        }
2859        $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_id;
2860        if (!$db->getResult($queryStr)) {
2861            $db->rollbackTransaction();
2862            return false;
2863        }
2864        $queryStr = "DELETE FROM `tblACLs` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
2865        if (!$db->getResult($queryStr)) {
2866            $db->rollbackTransaction();
2867            return false;
2868        }
2869        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id . " OR `target` = " . $this->_id;
2870        if (!$db->getResult($queryStr)) {
2871            $db->rollbackTransaction();
2872            return false;
2873        }
2874        $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = " . $this->_id;
2875        if (!$db->getResult($queryStr)) {
2876            $db->rollbackTransaction();
2877            return false;
2878        }
2879        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
2880        if (!$db->getResult($queryStr)) {
2881            $db->rollbackTransaction();
2882            return false;
2883        }
2884        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = " . $this->_id;
2885        if (!$db->getResult($queryStr)) {
2886            $db->rollbackTransaction();
2887            return false;
2888        }
2889
2890        // Delete the notification list.
2891        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
2892        if (!$db->getResult($queryStr)) {
2893            $db->rollbackTransaction();
2894            return false;
2895        }
2896
2897        $db->commitTransaction();
2898
2899        /* Check if 'onPostRemoveDocument' callback is set */
2900        if (isset($this->_dms->callbacks['onPostRemoveDocument'])) {
2901            foreach ($this->_dms->callbacks['onPostRemoveDocument'] as $callback) {
2902                if (!call_user_func($callback[0], $callback[1], $this)) {
2903                }
2904            }
2905        }
2906
2907        return true;
2908    } /* }}} */
2909
2910    /**
2911     * Get List of users and groups which have read access on the document.
2912     * The list will not include any guest users,
2913     * administrators and the owner of the document.
2914     *
2915     * This method is deprecated. Use
2916     * {@see SeedDMS_Core_Document::getReadAccessList()} instead.
2917     */
2918    protected function __getApproversList() { /* {{{ */
2919        return $this->getReadAccessList(0, 0, 0);
2920    } /* }}} */
2921
2922    /**
2923     * Returns a list of groups and users with read access on the document
2924     *
2925     * @param boolean $listadmin if set to true any admin will be listed too
2926     * @param boolean $listowner if set to true the owner will be listed too
2927     * @param boolean $listguest if set to true any guest will be listed too
2928     *
2929     * @return array list of users and groups
2930     */
2931    public function getReadAccessList($listadmin = 0, $listowner = 0, $listguest = 0) { /* {{{ */
2932        $db = $this->_dms->getDB();
2933
2934        $cachehash = substr(md5($listadmin.$listowner.$listguest), 0, 3);
2935        if (!isset($this->_readAccessList[$cachehash])) {
2936            $this->_readAccessList[$cachehash] = array("groups" => array(), "users" => array());
2937            $userIDs = "";
2938            $groupIDs = "";
2939            $defAccess  = $this->getDefaultAccess();
2940
2941            /* Check if the default access is < read access or >= read access.
2942             * If default access is less than read access, then create a list
2943             * of users and groups with read access.
2944             * If default access is equal or greater then read access, then
2945             * create a list of users and groups without read access.
2946             */
2947            if ($defAccess<M_READ) {
2948                // Get the list of all users and groups that are listed in the ACL as
2949                // having read access to the document.
2950                $tmpList = $this->getAccessList(M_READ, O_GTEQ);
2951            }
2952            else {
2953                // Get the list of all users and groups that DO NOT have read access
2954                // to the document.
2955                $tmpList = $this->getAccessList(M_NONE, O_LTEQ);
2956            }
2957            /** @var SeedDMS_Core_GroupAccess $groupAccess */
2958            foreach ($tmpList["groups"] as $groupAccess) {
2959                $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID();
2960            }
2961
2962            /** @var SeedDMS_Core_UserAccess $userAccess */
2963            foreach ($tmpList["users"] as $userAccess) {
2964                $user = $userAccess->getUser();
2965//                if (!$listadmin && $user->isAdmin()) continue;
2966//                if (!$listowner && $user->getID() == $this->_ownerID) continue;
2967//                if (!$listguest && $user->isGuest()) continue;
2968                $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $user->getID();
2969            }
2970
2971            // Construct a query against the users table to identify those users
2972            // that have read access on this document, either directly through an
2973            // ACL entry, by virtue of ownership or by having administrative rights
2974            // on the database.
2975            $queryStr = "";
2976            /* If default access is less then read, $userIDs and $groupIDs contains
2977             * a list of user with read access
2978             */
2979            if ($defAccess < M_READ) {
2980                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
2981                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
2982                    "WHERE 1=0".
2983                    ((strlen($groupIDs) > 0) ? " OR (`tblGroupMembers`.`groupID` IN (". $groupIDs ."))" : "").
2984                    ((strlen($userIDs) > 0) ?  " OR (`tblUsers`.`id` IN (". $userIDs ."))" : "").
2985                    " OR (`tblUsers`.`role` = ".SeedDMS_Core_User::role_admin.")".
2986                    " OR (`tblUsers`.`id` = ". $this->_ownerID . ")".
2987                    " ORDER BY `login`";
2988            }
2989            /* If default access is equal or greater than M_READ, $userIDs and
2990             * $groupIDs contains a list of user without read access
2991             * The sql statement will exclude those users and groups but include
2992             * admins and the owner
2993             */
2994            else {
2995                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
2996                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
2997                    "WHERE 1=1".
2998                    (strlen($groupIDs) == 0 ? "" : " AND (`tblGroupMembers`.`groupID` NOT IN (". $groupIDs .") OR `tblGroupMembers`.`groupID` IS NULL)").
2999                    (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))").
3000                    " OR `tblUsers`.`id` = ". $this->_ownerID . " OR `tblUsers`.`role` = ".SeedDMS_Core_User::role_admin." ORDER BY `login` ";
3001            }
3002            $resArr = $db->getResultArray($queryStr);
3003            if (!is_bool($resArr)) {
3004                foreach ($resArr as $row) {
3005                    $user = $this->_dms->getUser($row['id']);
3006                    if (!$listadmin && $user->isAdmin()) continue;
3007                    if (!$listowner && $user->getID() == $this->_ownerID) continue;
3008                    if (!$listguest && $user->isGuest()) continue;
3009                    $this->_readAccessList[$cachehash]["users"][] = $user;
3010                }
3011            }
3012
3013            // Assemble the list of groups that have read access to the document.
3014            $queryStr = "";
3015            if ($defAccess < M_READ) {
3016                if (strlen($groupIDs)>0) {
3017                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3018                        "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`";
3019                }
3020            }
3021            else {
3022                if (strlen($groupIDs)>0) {
3023                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3024                        "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`";
3025                }
3026                else {
3027                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`";
3028                }
3029            }
3030            if (strlen($queryStr)>0) {
3031                $resArr = $db->getResultArray($queryStr);
3032                if (!is_bool($resArr)) {
3033                    foreach ($resArr as $row) {
3034                        $group = $this->_dms->getGroup($row["id"]);
3035                        $this->_readAccessList[$cachehash]["groups"][] = $group;
3036                    }
3037                }
3038            }
3039        }
3040        return $this->_readAccessList[$cachehash];
3041    } /* }}} */
3042
3043    /**
3044     * Get the internally used folderList which stores the ids of folders from
3045     * the root folder to the parent folder.
3046     *
3047     * @return string column separated list of folder ids
3048     */
3049    public function getFolderList() { /* {{{ */
3050        $db = $this->_dms->getDB();
3051
3052        $queryStr = "SELECT `folderList` FROM `tblDocuments` WHERE id = ".$this->_id;
3053        $resArr = $db->getResultArray($queryStr);
3054        if (is_bool($resArr) && !$resArr)
3055            return false;
3056
3057        return $resArr[0]['folderList'];
3058    } /* }}} */
3059
3060    /**
3061     * Checks the internal data of the document and repairs it.
3062     * Currently, this function only repairs an incorrect folderList
3063     *
3064     * @return boolean true on success, otherwise false
3065     */
3066    public function repair() { /* {{{ */
3067        $db = $this->_dms->getDB();
3068
3069        $curfolderlist = $this->getFolderList();
3070
3071        // calculate the folderList of the folder
3072        $parent = $this->getFolder();
3073        $pathPrefix = "";
3074        $path = $parent->getPath();
3075        foreach ($path as $f) {
3076            $pathPrefix .= ":".$f->getID();
3077        }
3078        if (strlen($pathPrefix)>1) {
3079            $pathPrefix .= ":";
3080        }
3081        if ($curfolderlist != $pathPrefix) {
3082            $queryStr = "UPDATE `tblDocuments` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
3083            $res = $db->getResult($queryStr);
3084            if (!$res)
3085                return false;
3086        }
3087        return true;
3088    } /* }}} */
3089
3090    /**
3091     * Calculate the disk space including all versions of the document
3092     *
3093     * This is done by using the internal database field storing the
3094     * filesize of a document version.
3095     *
3096     * @return integer total disk space in Bytes
3097     */
3098    public function getUsedDiskSpace(): int { /* {{{ */
3099        $db = $this->_dms->getDB();
3100
3101        $queryStr = "SELECT SUM(`fileSize`) sum FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3102        $resArr = $db->getResultArray($queryStr);
3103        if (is_bool($resArr) && $resArr == false)
3104            return false;
3105
3106        return (int) $resArr[0]['sum'];
3107    } /* }}} */
3108
3109    /**
3110     * Returns a list of events happend during the life of the document
3111     *
3112     * This includes the creation of new versions, approval and reviews, etc.
3113     *
3114     * @return array list of events
3115     */
3116    public function getTimeline() { /* {{{ */
3117        $db = $this->_dms->getDB();
3118
3119        $timeline = array();
3120
3121        /* No need to add entries for new version because the status log
3122         * will generate an entry as well.
3123        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3124        $resArr = $db->getResultArray($queryStr);
3125        if (is_bool($resArr) && $resArr == false)
3126            return false;
3127
3128        foreach ($resArr as $row) {
3129            $date = date('Y-m-d H:i:s', $row['date']);
3130            $timeline[] = array('date'=>$date, 'msg'=>'Added version '.$row['version'], 'type'=>'add_version', 'version'=>$row['version'], 'document'=>$this, 'params'=>array($row['version']));
3131        }
3132         */
3133
3134        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3135        $resArr = $db->getResultArray($queryStr);
3136        if (is_bool($resArr) && $resArr == false)
3137            return false;
3138
3139        foreach ($resArr as $row) {
3140            $date = date('Y-m-d H:i:s', (int) $row['date']);
3141            $timeline[] = array('date'=>$date, 'msg'=>'Added attachment "'.$row['name'].'"', 'document'=>$this, 'type'=>'add_file', 'fileid'=>$row['id']);
3142        }
3143
3144        $queryStr =
3145            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`statusLogID`,`tblDocumentStatusLog`.`status`, ".
3146            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3147            "`tblDocumentStatusLog`.`userID` ".
3148            "FROM `tblDocumentStatus` ".
3149            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3150            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_id ."' ".
3151            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC";
3152        $resArr = $db->getResultArray($queryStr);
3153        if (is_bool($resArr) && !$resArr)
3154            return false;
3155
3156        /* The above query will also contain entries where a document status exists
3157         * but no status log entry. Those records will have no date and must be
3158         * skipped.
3159         */
3160        foreach ($resArr as $row) {
3161            if ($row['date']) {
3162                $date = $row['date'];
3163                $timeline[] = array('date'=>$date, 'msg'=>'Version '.$row['version'].': Status change to '.$row['status'], 'type'=>'status_change', 'version'=>$row['version'], 'document'=>$this, 'status'=>$row['status'], 'statusid'=>$row['statusID'], 'statuslogid'=>$row['statusLogID']);
3164            }
3165        }
3166        return $timeline;
3167    } /* }}} */
3168
3169    /**
3170     * Transfers the document to a new user
3171     *
3172     * This method not just sets a new owner of the document but also
3173     * transfers the document links, attachments and locks to the new user.
3174     *
3175     * @return boolean true if successful, otherwise false
3176     */
3177    public function transferToUser($newuser) { /* {{{ */
3178        $db = $this->_dms->getDB();
3179
3180        if ($newuser->getId() == $this->_ownerID)
3181            return true;
3182
3183        $db->startTransaction();
3184        $queryStr = "UPDATE `tblDocuments` SET `owner` = ".$newuser->getId()." WHERE `id` = " . $this->_id;
3185        if (!$db->getResult($queryStr)) {
3186            $db->rollbackTransaction();
3187            return false;
3188        }
3189
3190        $queryStr = "UPDATE `tblDocumentLocks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3191        if (!$db->getResult($queryStr)) {
3192            $db->rollbackTransaction();
3193            return false;
3194        }
3195
3196        $queryStr = "UPDATE `tblDocumentLinks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3197        if (!$db->getResult($queryStr)) {
3198            $db->rollbackTransaction();
3199            return false;
3200        }
3201
3202        $queryStr = "UPDATE `tblDocumentFiles` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3203        if (!$db->getResult($queryStr)) {
3204            $db->rollbackTransaction();
3205            return false;
3206        }
3207
3208        $this->_ownerID = $newuser->getID();
3209        $this->_owner = $newuser;
3210
3211        $db->commitTransaction();
3212        return true;
3213    } /* }}} */
3214
3215} /* }}} */
3216
3217
3218/**
3219 * Class to represent content of a document
3220 *
3221 * Each document has content attached to it, often called a 'version' of the
3222 * document. The document content represents a file on the disk with some
3223 * meta data stored in the database. A document content has a version number
3224 * which is incremented with each replacement of the old content. Old versions
3225 * are kept unless they are explicitly deleted by
3226 * {@see SeedDMS_Core_Document::removeContent()}.
3227 *
3228 * @category   DMS
3229 * @package    SeedDMS_Core
3230 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
3231 *             Uwe Steinmann <uwe@steinmann.cx>
3232 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
3233 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
3234 *             2010-2024 Uwe Steinmann
3235 * @version    Release: @package_version@
3236 */
3237class SeedDMS_Core_DocumentContent extends SeedDMS_Core_Object { /* {{{ */
3238    /**
3239     * @var object document
3240     */
3241    protected $_document;
3242
3243    /**
3244     * @var integer version
3245     */
3246    protected $_version;
3247
3248    /**
3249     * @var string comment
3250     */
3251    protected $_comment;
3252
3253    /**
3254     * @var string date
3255     */
3256    protected $_date;
3257
3258    /**
3259     * @var integer $_userID
3260     */
3261    protected $_userID;
3262
3263    /**
3264     * @var object $_user
3265     */
3266    protected $_user;
3267
3268    /**
3269     * @var string dir on disk (deprecated)
3270     */
3271    protected $_dir;
3272
3273    /**
3274     * @var string original file name
3275     */
3276    protected $_orgFileName;
3277
3278    /**
3279     * @var string file type (actually the extension without the leading dot)
3280     */
3281    protected $_fileType;
3282
3283    /**
3284     * @var string mime type
3285     */
3286    protected $_mimeType;
3287
3288    /**
3289     * @var string checksum of content
3290     */
3291    protected $_checksum;
3292
3293    /**
3294     * @var int size of content file
3295     */
3296    protected $_fileSize;
3297
3298    /**
3299     * @var object workflow
3300     */
3301    protected $_workflow;
3302
3303    /**
3304     * @var object workflow state
3305     */
3306    protected $_workflowState;
3307
3308    /**
3309     * @var int $_status state
3310     */
3311    protected $_status;
3312
3313    /**
3314     * @var int $_reviewStatus state
3315     */
3316    protected $_reviewStatus;
3317
3318    /**
3319     * @var int $_approvalStatus state
3320     */
3321    protected $_approvalStatus;
3322
3323    /**
3324     * @var array $_readAccessList
3325     */
3326    protected $_readAccessList;
3327
3328    /**
3329     * @var object dms
3330     */
3331    public $_dms;
3332
3333    /**
3334     * Recalculate the status of a document
3335     *
3336     * The methods checks the review and approval status and sets the
3337     * status of the document accordingly.
3338     *
3339     * If status is S_RELEASED and the version has a workflow, then set
3340     * the status to S_IN_WORKFLOW
3341     * If status is S_RELEASED and there are reviewers => set status S_DRAFT_REV
3342     * If status is S_RELEASED or S_DRAFT_REV and there are approvers => set
3343     * status S_DRAFT_APP
3344     * If status is draft and there are no approver and no reviewers => set
3345     * status to S_RELEASED
3346     * The status of a document with the current status S_OBSOLETE, S_REJECTED,
3347     * or S_EXPIRED will not be changed unless the parameter
3348     * $ignorecurrentstatus is set to true.
3349     *
3350     * This method may not be called after a negative approval or review to
3351     * recalculated the status, because
3352     * it doesn't take a defeating approval or review into account. This method
3353     * does not set the status to S_REJECTED! It will
3354     * just check for a pending workflow, approval or review and set the status
3355     * accordingly, e.g. after the list of reviewers or appovers has been
3356     * modified. If there is no pending workflow, approval or review the
3357     * status will be set to S_RELEASED.
3358     *
3359     * This method will call {@see SeedDMS_Core_DocumentContent::setStatus()}
3360     * which checks if the status has actually changed. This is, why this
3361     * function can be called at any time without harm to the status log.
3362     *
3363     * @param boolean $ignorecurrentstatus ignore the current status and
3364     *        recalculate a new status in any case
3365     * @param object $user the user initiating this method
3366     * @param string $msg message stored in status log when status is set
3367     */
3368    public function verifyStatus($ignorecurrentstatus = false, $user = null, $msg = '') { /* {{{ */
3369
3370        unset($this->_status);
3371        $st = $this->getStatus();
3372
3373        if (!$ignorecurrentstatus && ($st["status"]==S_OBSOLETE || $st["status"]==S_REJECTED || $st["status"]==S_EXPIRED )) return $st['status'];
3374
3375        $this->_workflow = null; // force to be reloaded from DB
3376        $hasworkflow =  $this->getWorkflow() ? true : false;
3377
3378        /* $pendingReview will be set when there are still open reviews */
3379        $pendingReview = false;
3380        /* $hasReview will be set if there is at least one positiv review */
3381        $hasReview = false;
3382        unset($this->_reviewStatus);  // force to be reloaded from DB
3383        $reviewStatus = $this->getReviewStatus();
3384        if (is_array($reviewStatus) && count($reviewStatus)>0) {
3385            foreach ($reviewStatus as $r) {
3386                if ($r["status"]==0) {
3387                    $pendingReview = true;
3388                    break;
3389                } elseif ($r["status"]==1) {
3390                    $hasReview = true;
3391                }
3392            }
3393        }
3394
3395        /* $pendingApproval will be set when there are still open approvals */
3396        $pendingApproval = false;
3397        /* $hasApproval will be set if there is at least one positiv review */
3398        $hasApproval = false;
3399        unset($this->_approvalStatus);  // force to be reloaded from DB
3400        $approvalStatus = $this->getApprovalStatus();
3401        if (is_array($approvalStatus) && count($approvalStatus)>0) {
3402            foreach ($approvalStatus as $a) {
3403                if ($a["status"]==0) {
3404                    $pendingApproval = true;
3405                    break;
3406                } elseif ($a["status"]==1) {
3407                    $hasApproval = true;
3408                }
3409            }
3410        }
3411
3412        /* First check for a running workflow or open reviews or approvals. */
3413        if ($hasworkflow) { $newstatus = S_IN_WORKFLOW; $ret = $this->setStatus(S_IN_WORKFLOW, $msg, $user); }
3414        elseif ($pendingReview) { $newstatus = S_DRAFT_REV; $ret = $this->setStatus(S_DRAFT_REV, $msg, $user); }
3415        elseif ($pendingApproval) { $newstatus = S_DRAFT_APP; $ret = $this->setStatus(S_DRAFT_APP, $msg, $user); }
3416        else { $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED, $msg, $user); }
3417        return $ret ? $newstatus : $ret;
3418    } /* }}} */
3419
3420    public function __construct($id, $document, $version, $comment, $date, $userID, $dir, $orgFileName, $fileType, $mimeType, $fileSize = 0, $checksum = '') { /* {{{ */
3421        parent::__construct($id);
3422        $this->_document = $document;
3423        $this->_version = (int) $version;
3424        $this->_comment = $comment;
3425        $this->_date = (int) $date;
3426        $this->_userID = (int) $userID;
3427        $this->_user = null;
3428        $this->_dir = $dir;
3429        $this->_orgFileName = $orgFileName;
3430        $this->_fileType = $fileType;
3431        $this->_mimeType = $mimeType;
3432        $this->_dms = $document->getDMS();
3433        if (!$fileSize) {
3434            if ($storage = $this->_dms->getStorage()) {
3435                $filesize = $storage->getContentFilesize($document, $this);
3436            } else {
3437                $this->_fileSize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->getPath());
3438            }
3439        } else {
3440            $this->_fileSize = (int) $fileSize;
3441        }
3442        $this->_checksum = $checksum;
3443        $this->_workflow = null;
3444        $this->_workflowState = null;
3445        $this->_readAccessList = null;
3446    } /* }}} */
3447
3448    /**
3449     * Check if this object is of type 'documentcontent'.
3450     *
3451     * @param string $type type of object
3452     */
3453    public function isType($type) { /* {{{ */
3454        return $type == 'documentcontent';
3455    } /* }}} */
3456
3457    public function getVersion() { return $this->_version; }
3458    public function getComment() { return $this->_comment; }
3459    public function getDate() { return $this->_date; }
3460    public function getOriginalFileName() { return $this->_orgFileName; }
3461    public function getFileType() { return $this->_fileType; }
3462    public function getFileName() { return $this->_version . $this->_fileType; }
3463    /**
3464     * getDir and the corresponding database table field are deprecated
3465     */
3466    private function __getDir() { return $this->_dir; }
3467    public function getMimeType() { return $this->_mimeType; }
3468    public function getDocument() { return $this->_document; }
3469
3470    public function getUser() { /* {{{ */
3471        if (!isset($this->_user))
3472            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
3473        return $this->_user;
3474    } /* }}} */
3475
3476    /**
3477     * Return path of file on disk relative to the content directory
3478     *
3479     * Since version 5.1.13 a single '.' in the fileType will be skipped.
3480     * On Windows a file named 'name.' will be saved as 'name' but the fileType
3481     * will contain the a single '.'.
3482     *
3483     * @return string path of file on disc
3484     */
3485    public function getPath() { return $this->_document->getDir() . $this->_version . $this->_fileType; }
3486
3487    /**
3488     * Set upload date of document content
3489     *
3490     * @param string $date date must be a timestamp or in the format 'Y-m-d H:i:s'
3491     *
3492     * @return boolean true on success, otherwise false
3493     */
3494    public function setDate($date = false) { /* {{{ */
3495        $db = $this->_document->getDMS()->getDB();
3496
3497        if (!$date)
3498            $date = time();
3499        else {
3500            if (is_string($date) && SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s')) {
3501                $date = strtotime($date);
3502            } elseif (is_numeric($date))
3503                $date = (int) $date;
3504            else
3505                return false;
3506        }
3507
3508        $queryStr = "UPDATE `tblDocumentContent` SET `date` = ". $date." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3509        if (!$db->getResult($queryStr))
3510            return false;
3511
3512        $this->_date = $date;
3513
3514        return true;
3515    } /* }}} */
3516
3517    public function getFileSize() { /* {{{ */
3518        return $this->_fileSize;
3519    } /* }}} */
3520
3521    /**
3522     * Set file size by reading the file
3523     */
3524    public function setFileSize() { /* {{{ */
3525        if ($storage = $this->_dms->getStorage()) {
3526            $filesize = $storage->getContentFilesize($this->_document, $this);
3527        } else {
3528            $filesize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3529        }
3530        if ($filesize === false)
3531            return false;
3532
3533        $db = $this->_document->getDMS()->getDB();
3534        $queryStr = "UPDATE `tblDocumentContent` SET `fileSize` = ".$filesize." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3535        if (!$db->getResult($queryStr))
3536            return false;
3537        $this->_fileSize = $filesize;
3538
3539        return true;
3540    } /* }}} */
3541
3542    public function getChecksum() { /* {{{ */
3543        return $this->_checksum;
3544    } /* }}} */
3545
3546    public function getRealChecksum() { /* {{{ */
3547        if ($storage = $this->_dms->getStorage()) {
3548            $checksum = $storage->getContentChecksum($this->_document, $this);
3549        } else {
3550            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->getPath());
3551        }
3552        return $checksum;
3553    } /* }}} */
3554
3555    /**
3556     * Set checksum by reading the file
3557     */
3558    public function setChecksum() { /* {{{ */
3559        if ($storage = $this->_dms->getStorage()) {
3560            $checksum = $storage->getContentChecksum($this->_document, $this);
3561        } else {
3562            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
3563        }
3564        if ($checksum === false)
3565            return false;
3566
3567        $db = $this->_document->getDMS()->getDB();
3568        $queryStr = "UPDATE `tblDocumentContent` SET `checksum` = ".$db->qstr($checksum)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3569        if (!$db->getResult($queryStr))
3570            return false;
3571        $this->_checksum = $checksum;
3572
3573        return true;
3574    } /* }}} */
3575
3576    public function getRealMimeType() { /* {{{ */
3577        if ($storage = $this->_dms->getStorage()) {
3578            $mimetype = $storage->getContentMimetype($this->_document, $this);
3579        } else {
3580            $mimetype = SeedDMS_Core_File::mimetype($this->_dms->contentDir . $this->getPath());
3581        }
3582        return $mimetype;
3583    } /* }}} */
3584
3585    /**
3586     * Set file type by evaluating the mime type
3587     */
3588    public function setFileType() { /* {{{ */
3589        $mimetype = $this->getMimeType();
3590
3591        $expect = SeedDMS_Core_File::fileExtension($mimetype);
3592        if ($expect && '.'.$expect != $this->_fileType) {
3593            $db = $this->_document->getDMS()->getDB();
3594            $db->startTransaction();
3595            $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` =   ". $this->_id;
3596            $res = $db->getResult($queryStr);
3597            if ($res) {
3598                if ($storage = $this->_dms->getStorage()) {
3599                    $err = $storage->setFileType($this->_document, $this, '.'.$expect);
3600                } else {
3601                    $err = SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect);
3602                }
3603                if (!$err) {
3604                    $db->rollbackTransaction();
3605                } else {
3606                    $this->_fileType = '.'.$expect;
3607                    $db->commitTransaction();
3608                    return true;
3609                }
3610            } else {
3611                $db->rollbackTransaction();
3612            }
3613        }
3614
3615        return false;
3616    } /* }}} */
3617
3618    public function setMimeType($newMimetype) { /* {{{ */
3619        $db = $this->_document->getDMS()->getDB();
3620
3621        if (!$newMimetype)
3622            return false;
3623
3624        $newMimetype = trim($newMimetype);
3625
3626        if (!$newMimetype)
3627            return false;
3628
3629        $queryStr = "UPDATE `tblDocumentContent` SET `mimeType` = ".$db->qstr($newMimetype)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3630        if (!$db->getResult($queryStr))
3631            return false;
3632
3633        $this->_mimeType = $newMimetype;
3634
3635        return true;
3636    } /* }}} */
3637
3638    public function setComment($newComment) { /* {{{ */
3639        $db = $this->_document->getDMS()->getDB();
3640
3641        /* Check if 'onPreSetVersionComment' callback is set */
3642        if (isset($this->_dms->callbacks['onPreSetVersionComment'])) {
3643            foreach ($this->_dms->callbacks['onPreSetVersionComment'] as $callback) {
3644                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
3645                if (is_bool($ret))
3646                    return $ret;
3647            }
3648        }
3649
3650        $queryStr = "UPDATE `tblDocumentContent` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
3651        if (!$db->getResult($queryStr))
3652            return false;
3653
3654        $oldComment = $this->_comment;
3655        $this->_comment = $newComment;
3656
3657        /* Check if 'onPostSetVersionComment' callback is set */
3658        if (isset($this->_dms->callbacks['onPostSetVersionComment'])) {
3659            foreach ($this->_dms->callbacks['onPostSetVersionComment'] as $callback) {
3660                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
3661                if (is_bool($ret))
3662                    return $ret;
3663            }
3664        }
3665
3666        return true;
3667    } /* }}} */
3668
3669    /**
3670     * Get the latest status of the content
3671     *
3672     * The status of the content reflects its current review, approval or workflow
3673     * state. A status can be a negative or positive number or 0. A negative
3674     * numbers indicate a missing approval, review or an obsolete content.
3675     * Positive numbers indicate some kind of approval or workflow being
3676     * active, but not necessarily a release.
3677     * S_DRAFT_REV, 0
3678     * S_DRAFT_APP, 1
3679     * S_RELEASED, 2
3680     * S_IN_WORKFLOW, 3
3681     * S_REJECTED, -1
3682     * S_OBSOLETE, -2
3683     * S_EXPIRED, -3
3684     * When a content is inserted and does not need approval nor review,
3685     * then its status is set to S_RELEASED immediately. Any change of
3686     * the status is monitored in the table tblDocumentStatusLog. This
3687     * function will always return the latest entry for the content.
3688     *
3689     * @return array latest record from tblDocumentStatusLog
3690     */
3691    public function getStatus($limit = 1) { /* {{{ */
3692        $db = $this->_document->getDMS()->getDB();
3693
3694        if (!is_numeric($limit)) return false;
3695
3696        // Retrieve the current overall status of the content represented by
3697        // this object.
3698        if (!isset($this->_status)) {
3699            $queryStr =
3700                "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
3701                "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3702                "`tblDocumentStatusLog`.`userID` ".
3703                "FROM `tblDocumentStatus` ".
3704                "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3705                "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
3706                "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
3707                "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC LIMIT ".(int) $limit;
3708
3709            $res = $db->getResultArray($queryStr);
3710            if (is_bool($res) && !$res)
3711                return false;
3712            if (count($res)!=1)
3713                return false;
3714            $this->_status = $res[0];
3715        }
3716        return $this->_status;
3717    } /* }}} */
3718
3719    /**
3720     * Get current and former states of the document content
3721     *
3722     * @param integer $limit if not set all log entries will be returned
3723     * @return array list of status changes
3724     */
3725    public function getStatusLog($limit = 0) { /* {{{ */
3726        $db = $this->_document->getDMS()->getDB();
3727
3728        if (!is_numeric($limit)) return false;
3729
3730        $queryStr =
3731            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
3732            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3733            "`tblDocumentStatusLog`.`userID` ".
3734            "FROM `tblDocumentStatus` ".
3735            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3736            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
3737            "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
3738            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC ";
3739        if ($limit)
3740            $queryStr .= "LIMIT ".(int) $limit;
3741
3742        $res = $db->getResultArray($queryStr);
3743        if (is_bool($res) && !$res)
3744            return false;
3745
3746        return $res;
3747    } /* }}} */
3748
3749    /**
3750     * Set the status of the content
3751     *
3752     * Setting the status means to add another entry into the table
3753     * tblDocumentStatusLog. The method returns also false if the status
3754     * is already set on the value passed to the method.
3755     *
3756     * @param integer $status     new status of content
3757     * @param string  $comment    comment for this status change
3758     * @param object  $updateUser user initiating the status change
3759     * @param string  $date       date in the format 'Y-m-d H:i:s'
3760     *
3761     * @return boolean true on success, otherwise false
3762     */
3763    public function setStatus(int $status, string $comment, $updateUser, $date = '') { /* {{{ */
3764        $db = $this->_document->getDMS()->getDB();
3765
3766        if (!is_numeric($status)) return false;
3767
3768        /* return an error if $updateuser is not set */
3769        if (!$updateUser || !$updateUser->isType('user'))
3770            return false;
3771
3772        // If the supplied value lies outside of the accepted range, return an
3773        // error.
3774        if ($status < S_LOWEST_STATUS || $status > S_HIGHEST_STATUS) {
3775            return false;
3776        }
3777
3778        // Retrieve the current overall status of the content represented by
3779        // this object, if it hasn't been done already.
3780        if (!isset($this->_status)) {
3781            $this->getStatus();
3782        }
3783        if ($this->_status["status"]==$status) {
3784            return true;
3785        }
3786        if ($date) {
3787            if (!SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s'))
3788                return false;
3789            $ddate = $db->qstr($date);
3790        } else
3791            $ddate = $db->getCurrentDatetime();
3792        $db->startTransaction();
3793        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
3794            "VALUES ('". $this->_status["statusID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$ddate.", '". $updateUser->getID() ."')";
3795        $res = $db->getResult($queryStr);
3796        if (is_bool($res) && !$res) {
3797            $db->rollbackTransaction();
3798            return false;
3799        }
3800
3801        /* Check if 'onSetStatus' callback is set */
3802        if (isset($this->_dms->callbacks['onSetStatus'])) {
3803            foreach ($this->_dms->callbacks['onSetStatus'] as $callback) {
3804                $ret = call_user_func($callback[0], $callback[1], $this, $updateUser, $this->_status["status"], $status);
3805                if (is_bool($ret)) {
3806                    unset($this->_status);
3807                    if ($ret)
3808                        $db->commitTransaction();
3809                    else
3810                        $db->rollbackTransaction();
3811                    return $ret;
3812                }
3813            }
3814        }
3815
3816        $db->commitTransaction();
3817        unset($this->_status);
3818        return true;
3819    } /* }}} */
3820
3821    /**
3822     * Rewrites the complete status log
3823     *
3824     * Attention: this function is highly dangerous.
3825     * It removes an existing status log and rewrites it.
3826     * This method was added for importing an xml dump.
3827     *
3828     * @param array $statuslog new status log with the newest log entry first.
3829     * @return boolean true on success, otherwise false
3830     */
3831    public function rewriteStatusLog($statuslog) { /* {{{ */
3832        $db = $this->_document->getDMS()->getDB();
3833
3834        $queryStr = "SELECT `tblDocumentStatus`.* FROM `tblDocumentStatus` WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentStatus`.`version` = '". $this->_version ."' ";
3835        $res = $db->getResultArray($queryStr);
3836        if (is_bool($res) && !$res)
3837            return false;
3838
3839        $statusID = $res[0]['statusID'];
3840
3841        $db->startTransaction();
3842
3843        /* First, remove the old entries */
3844        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID`=".$statusID;
3845        if (!$db->getResult($queryStr)) {
3846            $db->rollbackTransaction();
3847            return false;
3848        }
3849
3850        /* Second, insert the new entries */
3851        $statuslog = array_reverse($statuslog);
3852        foreach ($statuslog as $log) {
3853            if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
3854                $db->rollbackTransaction();
3855                return false;
3856            }
3857            $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
3858                "VALUES ('".$statusID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
3859            if (!$db->getResult($queryStr)) {
3860                $db->rollbackTransaction();
3861                return false;
3862            }
3863        }
3864
3865        $db->commitTransaction();
3866        return true;
3867    } /* }}} */
3868
3869
3870    /**
3871     * Returns the access mode similar to a document
3872     *
3873     * There is no real access mode for document content, so this is more
3874     * like a virtual access mode, derived from the status of the document
3875     * content. The function checks if {@see SeedDMS_Core_DMS::noReadForStatus}
3876     * contains the status of the version and returns M_NONE if it exists and
3877     * the user is not involved in a workflow or review/approval/revision.
3878     * This method is called by all functions that returns the content e.g.
3879     * {@see SeedDMS_Core_Document::getLatestContent()}
3880     * It is also used by {@see SeedDMS_Core_Document::getAccessMode()} to
3881     * prevent access on the whole document if there is no accessible version.
3882     *
3883     * FIXME: This method only works propperly if $u is the currently logged in
3884     * user, because noReadForStatus will be set for this user.
3885     * FIXED: instead of using $dms->noReadForStatus it is take from the user's role
3886     *
3887     * @param object $u user
3888     * @return integer either M_NONE or M_READ
3889     */
3890    public function getAccessMode($u) { /* {{{ */
3891        $dms = $this->_document->getDMS();
3892
3893        /* Check if 'onCheckAccessDocumentContent' callback is set */
3894        if (isset($this->_dms->callbacks['onCheckAccessDocumentContent'])) {
3895            foreach ($this->_dms->callbacks['onCheckAccessDocumentContent'] as $callback) {
3896                if (($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
3897                    return $ret;
3898                }
3899            }
3900        }
3901
3902        return M_READ;
3903
3904        if (!$u)
3905            return M_NONE;
3906
3907        /* If read access isn't further restricted by status, than grant read access */
3908        if (!$dms->noReadForStatus)
3909            return M_READ;
3910        $noReadForStatus = $dms->noReadForStatus;
3911
3912        /* If the current status is not in list of status without read access, then grant read access */
3913        if (!in_array($this->getStatus()['status'], $noReadForStatus))
3914            return M_READ;
3915
3916        /* Administrators have unrestricted access */
3917        if ($u->isAdmin()) return M_READ;
3918
3919        /* The owner of the document has unrestricted access */
3920        $owner = $this->_document->getOwner();
3921        if ($u->getID() == $owner->getID()) return M_READ;
3922
3923        /* Read/Write access on the document will also grant access on the version */
3924        if ($this->_document->getAccessMode($u) >= M_READWRITE) return M_READ;
3925
3926        /* At this point the current status is in the list of status without read access.
3927         * The only way to still gain read access is, if the user is involved in the
3928         * process, e.g. is a reviewer, approver or an active person in the workflow.
3929         */
3930        $s = $this->getStatus();
3931        switch($s['status']) {
3932        case S_DRAFT_REV:
3933            $status = $this->getReviewStatus();
3934            foreach ($status as $r) {
3935                if ($r['status'] != -2) // Check if reviewer was removed
3936                    switch ($r["type"]) {
3937                    case 0: // Reviewer is an individual.
3938                        if ($u->getId() == $r["required"])
3939                            return M_READ;
3940                        break;
3941                    case 1: // Reviewer is a group.
3942                        $required = $dms->getGroup($r["required"]);
3943                        if (is_object($required) && $required->isMember($u))
3944                            return M_READ;
3945                        break;
3946                    }
3947            }
3948            break;
3949        case S_DRAFT_APP:
3950            $status = $this->getApprovalStatus();
3951            foreach ($status as $r) {
3952                if ($r['status'] != -2) // Check if approver was removed
3953                    switch ($r["type"]) {
3954                    case 0: // Reviewer is an individual.
3955                        if ($u->getId() == $r["required"])
3956                            return M_READ;
3957                        break;
3958                    case 1: // Reviewer is a group.
3959                        $required = $dms->getGroup($r["required"]);
3960                        if (is_object($required) && $required->isMember($u))
3961                            return M_READ;
3962                        break;
3963                    }
3964            }
3965            break;
3966        case S_RELEASED:
3967            break;
3968        case S_IN_WORKFLOW:
3969            if (!$this->_workflow)
3970                $this->getWorkflow();
3971
3972            if ($this->_workflow) {
3973                if (!$this->_workflowState)
3974                    $this->getWorkflowState();
3975                $transitions = $this->_workflow->getNextTransitions($this->_workflowState);
3976                foreach ($transitions as $transition) {
3977                    if ($this->triggerWorkflowTransitionIsAllowed($u, $transition))
3978                        return M_READ;
3979                }
3980            }
3981            break;
3982        case S_REJECTED:
3983            break;
3984        case S_OBSOLETE:
3985            break;
3986        case S_EXPIRED:
3987            break;
3988        }
3989
3990        return M_NONE;
3991    } /* }}} */
3992
3993    /**
3994     * Return a list of all reviewers separated by individuals and groups
3995     * This list will not take the review log into account. Therefore it
3996     * can contain reviewers which has actually been deleted as a reviewer.
3997     *
3998     * @return array|bool|null
3999     */
4000    public function getReviewers() { /* {{{ */
4001        $dms = $this->_document->getDMS();
4002        $db = $dms->getDB();
4003
4004        $queryStr =
4005            "SELECT * FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4006            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4007
4008        $recs = $db->getResultArray($queryStr);
4009        if (is_bool($recs))
4010            return false;
4011        $reviewers = array('i'=>array(), 'g'=>array());
4012        foreach ($recs as $rec) {
4013            if ($rec['type'] == 0) {
4014                if ($u = $dms->getUser($rec['required']))
4015                    $reviewers['i'][] = $u;
4016            } elseif ($rec['type'] == 1) {
4017                if ($g = $dms->getGroup($rec['required']))
4018                    $reviewers['g'][] = $g;
4019            }
4020        }
4021        return $reviewers;
4022    } /* }}} */
4023
4024    /**
4025     * Get the current review status of the document content
4026     * The review status is a list of reviewers and its current status
4027     *
4028     * @param integer $limit the number of recent status changes per reviewer
4029     * @return array list of review status
4030     */
4031    public function getReviewStatus($limit = 1) { /* {{{ */
4032        $db = $this->_document->getDMS()->getDB();
4033
4034        if (!is_numeric($limit)) return false;
4035
4036        // Retrieve the current status of each assigned reviewer for the content
4037        // represented by this object.
4038        // FIXME: caching was turned off to make list of review log in ViewDocument
4039        // possible
4040        if (1 || !isset($this->_reviewStatus)) {
4041            /* First get a list of all reviews for this document content */
4042            $queryStr =
4043                "SELECT `reviewID` FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4044                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4045            $recs = $db->getResultArray($queryStr);
4046            if (is_bool($recs) && !$recs)
4047                return false;
4048            $this->_reviewStatus = array();
4049            if ($recs) {
4050                foreach ($recs as $rec) {
4051                    $queryStr =
4052                        "SELECT `tblDocumentReviewers`.*, `tblDocumentReviewLog`.`reviewLogID`, `tblDocumentReviewLog`.`status`, ".
4053                        "`tblDocumentReviewLog`.`comment`, `tblDocumentReviewLog`.`date`, ".
4054                        "`tblDocumentReviewLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4055                        "FROM `tblDocumentReviewers` ".
4056                        "LEFT JOIN `tblDocumentReviewLog` USING (`reviewID`) ".
4057                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentReviewers`.`required`".
4058                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentReviewers`.`required`".
4059                        "WHERE `tblDocumentReviewers`.`reviewID` = '". $rec['reviewID'] ."' ".
4060                        "ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4061
4062                    $res = $db->getResultArray($queryStr);
4063                    if (is_bool($res) && !$res) {
4064                        unset($this->_reviewStatus);
4065                        return false;
4066                    }
4067                    foreach ($res as &$t) {
4068                        $filename = $this->_dms->contentDir . $this->_document->getDir().'r'.$t['reviewLogID'];
4069                        if (SeedDMS_Core_File::file_exists($filename))
4070                            $t['file'] = $filename;
4071                        else
4072                            $t['file'] = '';
4073                    }
4074                    $this->_reviewStatus = array_merge($this->_reviewStatus, $res);
4075                }
4076            }
4077        }
4078        return $this->_reviewStatus;
4079    } /* }}} */
4080
4081    /**
4082     * Get the latest entries from the review log of the document content
4083     *
4084     * @param integer $limit the number of log entries returned, defaults to 1
4085     * @return array list of review log entries
4086     */
4087    public function getReviewLog($limit = 1) { /* {{{ */
4088        $db = $this->_document->getDMS()->getDB();
4089
4090        if (!is_numeric($limit)) return false;
4091
4092        $queryStr =
4093            "SELECT * FROM `tblDocumentReviewLog` LEFT JOIN `tblDocumentReviewers` ON  `tblDocumentReviewLog`.`reviewID` = `tblDocumentReviewers`.`reviewID` WHERE `version`='".$this->_version
4094            ."' AND `documentID` = '". $this->_document->getID() ."' "
4095            ."ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4096        $recs = $db->getResultArray($queryStr);
4097        if (is_bool($recs) && !$recs)
4098            return false;
4099        return($recs);
4100    } /* }}} */
4101
4102    /**
4103     * Rewrites the complete review log
4104     *
4105     * Attention: this function is highly dangerous.
4106     * It removes an existing review log and rewrites it.
4107     * This method was added for importing an xml dump.
4108     *
4109     * @param array $reviewlog new status log with the newest log entry first.
4110     * @return boolean true on success, otherwise false
4111     */
4112    public function rewriteReviewLog($reviewers) { /* {{{ */
4113        $db = $this->_document->getDMS()->getDB();
4114
4115        $queryStr = "SELECT `tblDocumentReviewers`.* FROM `tblDocumentReviewers` WHERE `tblDocumentReviewers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentReviewers`.`version` = '". $this->_version ."' ";
4116        $res = $db->getResultArray($queryStr);
4117        if (is_bool($res) && !$res)
4118            return false;
4119
4120        $db->startTransaction();
4121
4122        if ($res) {
4123            foreach ($res as $review) {
4124                $reviewID = $review['reviewID'];
4125
4126                /* First, remove the old entries */
4127                $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `reviewID`=".$reviewID;
4128                if (!$db->getResult($queryStr)) {
4129                    $db->rollbackTransaction();
4130                    return false;
4131                }
4132
4133                $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `reviewID`=".$reviewID;
4134                if (!$db->getResult($queryStr)) {
4135                    $db->rollbackTransaction();
4136                    return false;
4137                }
4138            }
4139        }
4140
4141        /* Second, insert the new entries */
4142        foreach ($reviewers as $review) {
4143            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4144                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4145            if (!$db->getResult($queryStr)) {
4146                $db->rollbackTransaction();
4147                return false;
4148            }
4149            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4150            $reviewlog = array_reverse($review['logs']);
4151            foreach ($reviewlog as $log) {
4152                if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4153                    $db->rollbackTransaction();
4154                    return false;
4155                }
4156                $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4157                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4158                if (!$db->getResult($queryStr)) {
4159                    $db->rollbackTransaction();
4160                    return false;
4161                }
4162                $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4163                if (!empty($log['file'])) {
4164                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4165                }
4166            }
4167        }
4168
4169        $db->commitTransaction();
4170        return true;
4171    } /* }}} */
4172
4173    /**
4174     * Return a list of all approvers separated by individuals and groups
4175     * This list will not take the approval log into account. Therefore it
4176     * can contain approvers which has actually been deleted as an approver.
4177     *
4178     * @return array|bool|null
4179     */
4180    public function getApprovers() { /* {{{ */
4181        $dms = $this->_document->getDMS();
4182        $db = $dms->getDB();
4183
4184        $queryStr =
4185            "SELECT * FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4186            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4187
4188        $recs = $db->getResultArray($queryStr);
4189        if (is_bool($recs))
4190            return false;
4191        $approvers = array('i'=>array(), 'g'=>array());
4192        foreach ($recs as $rec) {
4193            if ($rec['type'] == 0) {
4194                if ($u = $dms->getUser($rec['required']))
4195                    $approvers['i'][] = $u;
4196            } elseif ($rec['type'] == 1) {
4197                if ($g = $dms->getGroup($rec['required']))
4198                    $approvers['g'][] = $g;
4199            }
4200        }
4201        return $approvers;
4202    } /* }}} */
4203
4204    /**
4205     * Get the current approval status of the document content
4206     * The approval status is a list of approvers and its current status
4207     *
4208     * @param integer $limit the number of recent status changes per approver
4209     * @return array list of approval status
4210     */
4211    public function getApprovalStatus($limit = 1) { /* {{{ */
4212        $db = $this->_document->getDMS()->getDB();
4213
4214        if (!is_numeric($limit)) return false;
4215
4216        // Retrieve the current status of each assigned approver for the content
4217        // represented by this object.
4218        // FIXME: caching was turned off to make list of approval log in ViewDocument
4219        // possible
4220        if (1 || !isset($this->_approvalStatus)) {
4221            /* First get a list of all approvals for this document content */
4222            $queryStr =
4223                "SELECT `approveID` FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4224                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4225            $recs = $db->getResultArray($queryStr);
4226            if (is_bool($recs) && !$recs)
4227                return false;
4228            $this->_approvalStatus = array();
4229            if ($recs) {
4230                foreach ($recs as $rec) {
4231                    $queryStr =
4232                        "SELECT `tblDocumentApprovers`.*, `tblDocumentApproveLog`.`approveLogID`, `tblDocumentApproveLog`.`status`, ".
4233                        "`tblDocumentApproveLog`.`comment`, `tblDocumentApproveLog`.`date`, ".
4234                        "`tblDocumentApproveLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4235                        "FROM `tblDocumentApprovers` ".
4236                        "LEFT JOIN `tblDocumentApproveLog` USING (`approveID`) ".
4237                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentApprovers`.`required` ".
4238                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentApprovers`.`required`".
4239                        "WHERE `tblDocumentApprovers`.`approveID` = '". $rec['approveID'] ."' ".
4240                        "ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4241
4242                    $res = $db->getResultArray($queryStr);
4243                    if (is_bool($res) && !$res) {
4244                        unset($this->_approvalStatus);
4245                        return false;
4246                    }
4247                    foreach ($res as &$t) {
4248                        $filename = $this->_dms->contentDir . $this->_document->getDir().'a'.$t['approveLogID'];
4249                        if (SeedDMS_Core_File::file_exists($filename))
4250                            $t['file'] = $filename;
4251                        else
4252                            $t['file'] = '';
4253                    }
4254                    $this->_approvalStatus = array_merge($this->_approvalStatus, $res);
4255                }
4256            }
4257        }
4258        return $this->_approvalStatus;
4259    } /* }}} */
4260
4261    /**
4262     * Get the latest entries from the approval log of the document content
4263     *
4264     * @param integer $limit the number of log entries returned, defaults to 1
4265     * @return array list of approval log entries
4266     */
4267    public function getApproveLog($limit = 1) { /* {{{ */
4268        $db = $this->_document->getDMS()->getDB();
4269
4270        if (!is_numeric($limit)) return false;
4271
4272        $queryStr =
4273            "SELECT * FROM `tblDocumentApproveLog` LEFT JOIN `tblDocumentApprovers` ON  `tblDocumentApproveLog`.`approveID` = `tblDocumentApprovers`.`approveID` WHERE `version`='".$this->_version
4274            ."' AND `documentID` = '". $this->_document->getID() ."' "
4275            ."ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4276        $recs = $db->getResultArray($queryStr);
4277        if (is_bool($recs) && !$recs)
4278            return false;
4279        return($recs);
4280    } /* }}} */
4281
4282    /**
4283     * Rewrites the complete approval log
4284     *
4285     * Attention: this function is highly dangerous.
4286     * It removes an existing review log and rewrites it.
4287     * This method was added for importing an xml dump.
4288     *
4289     * @param array $reviewlog new status log with the newest log entry first.
4290     * @return boolean true on success, otherwise false
4291     */
4292    public function rewriteApprovalLog($reviewers) { /* {{{ */
4293        $db = $this->_document->getDMS()->getDB();
4294
4295        $queryStr = "SELECT `tblDocumentApprovers`.* FROM `tblDocumentApprovers` WHERE `tblDocumentApprovers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentApprovers`.`version` = '". $this->_version ."' ";
4296        $res = $db->getResultArray($queryStr);
4297        if (is_bool($res) && !$res)
4298            return false;
4299
4300        $db->startTransaction();
4301
4302        if ($res) {
4303            foreach ($res as $review) {
4304                $reviewID = $review['reviewID'];
4305
4306                /* First, remove the old entries */
4307                $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `approveID`=".$reviewID;
4308                if (!$db->getResult($queryStr)) {
4309                    $db->rollbackTransaction();
4310                    return false;
4311                }
4312
4313                $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `approveID`=".$reviewID;
4314                if (!$db->getResult($queryStr)) {
4315                    $db->rollbackTransaction();
4316                    return false;
4317                }
4318            }
4319        }
4320
4321        /* Second, insert the new entries */
4322        foreach ($reviewers as $review) {
4323            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4324                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4325            if (!$db->getResult($queryStr)) {
4326                $db->rollbackTransaction();
4327                return false;
4328            }
4329            $reviewID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4330            $reviewlog = array_reverse($review['logs']);
4331            foreach ($reviewlog as $log) {
4332                if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4333                    $db->rollbackTransaction();
4334                    return false;
4335                }
4336                $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4337                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4338                if (!$db->getResult($queryStr)) {
4339                    $db->rollbackTransaction();
4340                    return false;
4341                }
4342                $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4343                if (!empty($log['file'])) {
4344                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4345                }
4346            }
4347        }
4348
4349        $db->commitTransaction();
4350        return true;
4351    } /* }}} */
4352
4353    /**
4354     * Add user as new reviewer
4355     *
4356     * @param object $user user in charge for the review
4357     * @param object $requestUser user requesting the operation (usually the
4358     * currently logged in user)
4359     *
4360     * @return integer|false if > 0 the id of the review log, if < 0 the error
4361     * code, false in case of an sql error
4362     */
4363    public function addIndReviewer($user, $requestUser) { /* {{{ */
4364        if (!$user || !$requestUser)
4365            return -1;
4366
4367        $db = $this->_document->getDMS()->getDB();
4368
4369        if (!$user->isType('user'))
4370            return -1;
4371
4372        $userID = $user->getID();
4373
4374        // Get the list of users and groups with read access to this document.
4375        if ($this->_document->getAccessMode($user) < M_READ) {
4376            return -2;
4377        }
4378
4379        // Check to see if the user has already been added to the review list.
4380        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
4381        if (is_bool($reviewStatus) && !$reviewStatus) {
4382            return false;
4383        }
4384        $indstatus = false;
4385        if (count($reviewStatus["indstatus"]) > 0) {
4386            $indstatus = array_pop($reviewStatus["indstatus"]);
4387            if ($indstatus["status"]!=-2) {
4388                // User is already on the list of reviewers; return an error.
4389                return -3;
4390            }
4391        }
4392
4393        // Add the user into the review database.
4394        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
4395            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4396                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
4397            $res = $db->getResult($queryStr);
4398            if (is_bool($res) && !$res) {
4399                return false;
4400            }
4401            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4402        }
4403        else {
4404            $reviewID = isset($indstatus["reviewID"]) ? $indstatus["reviewID"] : null;
4405        }
4406
4407        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4408            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4409        $res = $db->getResult($queryStr);
4410        if (is_bool($res) && !$res) {
4411            return false;
4412        }
4413
4414        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4415        $db->dropTemporaryTable('ttreviewid');
4416        return $reviewLogID;
4417    } /* }}} */
4418
4419    /**
4420     * Add group as new reviewer
4421     *
4422     * @param object $group group in charge for the review
4423     * @param object $requestUser user requesting the operation (usually the
4424     * currently logged in user)
4425     *
4426     * @return integer|false if > 0 the id of the review log, if < 0 the error
4427     * code, false in case of an sql error
4428     */
4429    public function addGrpReviewer($group, $requestUser) { /* {{{ */
4430        if (!$group || !$requestUser)
4431            return -1;
4432
4433        $db = $this->_document->getDMS()->getDB();
4434
4435        if (!$group->isType('group'))
4436            return -1;
4437
4438        $groupID = $group->getID();
4439
4440        // Get the list of users and groups with read access to this document.
4441        if (!isset($this->_readAccessList)) {
4442            // TODO: error checking.
4443            $this->_readAccessList = $this->_document->getReadAccessList();
4444        }
4445        $approved = false;
4446        foreach ($this->_readAccessList["groups"] as $appGroup) {
4447            if ($groupID == $appGroup->getID()) {
4448                $approved = true;
4449                break;
4450            }
4451        }
4452        if (!$approved) {
4453            return -2;
4454        }
4455
4456        // Check to see if the group has already been added to the review list.
4457        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
4458        if (is_bool($reviewStatus) && !$reviewStatus) {
4459            return false;
4460        }
4461        if (count($reviewStatus) > 0 && $reviewStatus[0]["status"]!=-2) {
4462            // Group is already on the list of reviewers; return an error.
4463            return -3;
4464        }
4465
4466        // Add the group into the review database.
4467        if (!isset($reviewStatus[0]["status"]) || (isset($reviewStatus[0]["status"]) && $reviewStatus[0]["status"]!=-2)) {
4468            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4469                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
4470            $res = $db->getResult($queryStr);
4471            if (is_bool($res) && !$res) {
4472                return false;
4473            }
4474            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4475        }
4476        else {
4477            $reviewID = isset($reviewStatus[0]["reviewID"]) ? $reviewStatus[0]["reviewID"] : null;
4478        }
4479
4480        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4481            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4482        $res = $db->getResult($queryStr);
4483        if (is_bool($res) && !$res) {
4484            return false;
4485        }
4486
4487        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4488        $db->dropTemporaryTable('ttreviewid');
4489        return $reviewLogID;
4490    } /* }}} */
4491
4492    /**
4493     * Add a review to the document content
4494     *
4495     * This method will add an entry to the table tblDocumentReviewLog.
4496     * It will first check if the user is ment to review the document version.
4497     * It not the return value is -3.
4498     * Next it will check if the users has been removed from the list of
4499     * reviewers. In that case -4 will be returned.
4500     * If the given review status has been set by the user before, it cannot
4501     * be set again and 0 will be returned. Ð†f the review could be succesfully
4502     * added, the review log id will be returned.
4503     *
4504     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
4505     *
4506     * @param object  $user user doing the review
4507     * @param object  $requestUser user asking for the review, this is mostly
4508     * the user currently logged in.
4509     * @param integer $status status of review
4510     * @param string  $comment comment for review
4511     *
4512     * @return integer|bool new review log id, error code 0 till -4,
4513     * false in case of an sql error
4514     */
4515    public function setReviewByInd($user, $requestUser, $status, $comment, $file = '') { /* {{{ */
4516        if (!$user || !$requestUser)
4517            return -1;
4518
4519        $db = $this->_document->getDMS()->getDB();
4520
4521        if (!$user->isType('user'))
4522            return -1;
4523
4524        // Check if the user is on the review list at all.
4525        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
4526        if (is_bool($reviewStatus) && !$reviewStatus) {
4527            return false;
4528        }
4529        if (count($reviewStatus["indstatus"])==0) {
4530            // User is not assigned to review this document. No action required.
4531            // Return an error.
4532            return -3;
4533        }
4534        $indstatus = array_pop($reviewStatus["indstatus"]);
4535        if ($indstatus["status"]==-2) {
4536            // User has been deleted from reviewers
4537            return -4;
4538        }
4539        // Check if the status is really different from the current status
4540        if ($indstatus["status"] == $status)
4541            return 0;
4542
4543        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4544            `comment`, `date`, `userID`) ".
4545            "VALUES ('". $indstatus["reviewID"] ."', '".
4546            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4547            $requestUser->getID() ."')";
4548        $res = $db->getResult($queryStr);
4549        if (is_bool($res) && !$res)
4550            return false;
4551
4552        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4553        if ($file) {
4554            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4555        }
4556        return $reviewLogID;
4557    } /* }}} */
4558
4559    /**
4560     * Add another entry to review log which resets the status
4561     *
4562     * This method will not delete anything from the database, but will add
4563     * a new review log entry which sets the status to 0. This is only allowed
4564     * if the current status is either 1 (reviewed) or -1 (rejected).
4565     *
4566     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
4567     * should be called to recalculate the document status.
4568     *
4569     * @param integer $reviewid id of review
4570     * @param SeedDMS_Core_User $requestUser user requesting the removal
4571     * @param string $comment comment
4572     *
4573     * @return integer|bool true if successful, error code < 0,
4574     * false in case of an sql error
4575     */
4576    public function removeReview($reviewid, $requestUser, $comment = '') { /* {{{ */
4577        $db = $this->_document->getDMS()->getDB();
4578
4579        // Check to see if the user can be removed from the review list.
4580        $reviews = $this->getReviewStatus();
4581        if (is_bool($reviews) && !$reviews) {
4582            return false;
4583        }
4584        $reviewStatus = null;
4585        foreach ($reviews as $review) {
4586            if ($review['reviewID'] == $reviewid) {
4587                $reviewStatus = $review;
4588                break;
4589            }
4590        }
4591        if (!$reviewStatus)
4592            return -2;
4593
4594        // The review log entry may only be removed if the status is 1 or -1
4595        if ($reviewStatus["status"] != 1 && $reviewStatus["status"] != -1)
4596            return -3;
4597
4598        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4599            `comment`, `date`, `userID`) ".
4600            "VALUES ('". $reviewStatus["reviewID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4601            $requestUser->getID() ."')";
4602        $res = $db->getResult($queryStr);
4603        if (is_bool($res) && !$res)
4604            return false;
4605
4606        return true;
4607    } /* }}} */
4608
4609    /**
4610     * Add a review to the document content
4611     *
4612     * This method is similar to
4613     * {@see SeedDMS_Core_DocumentContent::setReviewByInd()} but adds a review
4614     * for a group instead of a user.
4615     *
4616     * @param object  $group group doing the review
4617     * @param object  $requestUser user asking for the review, this is mostly
4618     * the user currently logged in.
4619     * @param integer $status status of review
4620     * @param string  $comment comment for review
4621     *
4622     * @return integer|bool new review log id, error code 0 till -4,
4623     * false in case of an sql error
4624     */
4625    public function setReviewByGrp($group, $requestUser, $status, $comment, $file = '') { /* {{{ */
4626        if (!$group || !$requestUser)
4627            return -1;
4628
4629        $db = $this->_document->getDMS()->getDB();
4630
4631        if (!$group->isType('group'))
4632                return -1;
4633
4634        // Check if the group is on the review list at all.
4635        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
4636        if (is_bool($reviewStatus) && !$reviewStatus) {
4637            return false;
4638        }
4639        if (count($reviewStatus)==0) {
4640            // User is not assigned to review this document. No action required.
4641            // Return an error.
4642            return -3;
4643        }
4644        if ((int) $reviewStatus[0]["status"]==-2) {
4645            // Group has been deleted from reviewers
4646            return -4;
4647        }
4648
4649        // Check if the status is really different from the current status
4650        if ($reviewStatus[0]["status"] == $status)
4651            return 0;
4652
4653        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
4654            `comment`, `date`, `userID`) ".
4655            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".
4656            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4657            $requestUser->getID() ."')";
4658        $res = $db->getResult($queryStr);
4659        if (is_bool($res) && !$res)
4660            return false;
4661
4662        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4663        if ($file) {
4664            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4665        }
4666        return $reviewLogID;
4667 } /* }}} */
4668
4669    /**
4670     * Add user as new approver
4671     *
4672     * @param object $user user in charge for the approval
4673     * @param object $requestUser user requesting the operation (usually the
4674     * currently logged in user)
4675     *
4676     * @return integer|false if > 0 the id of the approval log, if < 0 the error
4677     * code, false in case of an sql error
4678     */
4679    public function addIndApprover($user, $requestUser) { /* {{{ */
4680        if (!$user || !$requestUser)
4681            return -1;
4682
4683        $db = $this->_document->getDMS()->getDB();
4684
4685        if (!$user->isType('user'))
4686            return -1;
4687
4688        $userID = $user->getID();
4689
4690        // Get the list of users and groups with read access to this document.
4691        if ($this->_document->getAccessMode($user) < M_READ) {
4692            return -2;
4693        }
4694
4695        // Check if the user has already been added to the approvers list.
4696        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
4697        if (is_bool($approvalStatus) && !$approvalStatus) {
4698            return false;
4699        }
4700        $indstatus = false;
4701        if (count($approvalStatus["indstatus"]) > 0) {
4702            $indstatus = array_pop($approvalStatus["indstatus"]);
4703            if ($indstatus["status"]!=-2) {
4704                // User is already on the list of approverss; return an error.
4705                return -3;
4706            }
4707        }
4708
4709        if (!$indstatus || (isset($indstatus["status"]) && $indstatus["status"]!=-2)) {
4710            // Add the user into the approvers database.
4711            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4712                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
4713            $res = $db->getResult($queryStr);
4714            if (is_bool($res) && !$res) {
4715                return false;
4716            }
4717            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4718        }
4719        else {
4720            $approveID = isset($indstatus["approveID"]) ? $indstatus["approveID"] : null;
4721        }
4722
4723        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4724            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4725        $res = $db->getResult($queryStr);
4726        if (is_bool($res) && !$res) {
4727            return false;
4728        }
4729
4730        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4731        $db->dropTemporaryTable('ttapproveid');
4732        return $approveLogID;
4733    } /* }}} */
4734
4735    /**
4736     * Add group as new approver
4737     *
4738     * @param object $group group in charge for the approval
4739     * @param object $requestUser user requesting the operation (usually the
4740     * currently logged in user)
4741     *
4742     * @return integer|false if > 0 the id of the approval log, if < 0 the error
4743     * code, false in case of an sql error
4744     */
4745    public function addGrpApprover($group, $requestUser) { /* {{{ */
4746        if (!$group || !$requestUser)
4747            return -1;
4748
4749        $db = $this->_document->getDMS()->getDB();
4750
4751        if (!$group->isType('group'))
4752            return -1;
4753
4754        $groupID = $group->getID();
4755
4756        // Get the list of users and groups with read access to this document.
4757        if (!isset($this->_readAccessList)) {
4758            // TODO: error checking.
4759            $this->_readAccessList = $this->_document->getReadAccessList();
4760        }
4761        $approved = false;
4762        foreach ($this->_readAccessList["groups"] as $appGroup) {
4763            if ($groupID == $appGroup->getID()) {
4764                $approved = true;
4765                break;
4766            }
4767        }
4768        if (!$approved) {
4769            return -2;
4770        }
4771
4772        // Check if the group has already been added to the approver list.
4773        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
4774        if (is_bool($approvalStatus) && !$approvalStatus) {
4775            return false;
4776        }
4777        if (count($approvalStatus) > 0 && $approvalStatus[0]["status"]!=-2) {
4778            // Group is already on the list of approvers; return an error.
4779            return -3;
4780        }
4781
4782        // Add the group into the approver database.
4783        if (!isset($approvalStatus[0]["status"]) || (isset($approvalStatus[0]["status"]) && $approvalStatus[0]["status"]!=-2)) {
4784            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4785                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
4786            $res = $db->getResult($queryStr);
4787            if (is_bool($res) && !$res) {
4788                return false;
4789            }
4790            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4791        }
4792        else {
4793            $approveID = isset($approvalStatus[0]["approveID"]) ? $approvalStatus[0]["approveID"] :null;
4794        }
4795
4796        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4797            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
4798        $res = $db->getResult($queryStr);
4799        if (is_bool($res) && !$res) {
4800            return false;
4801        }
4802
4803        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4804        $db->dropTemporaryTable('ttapproveid');
4805        return $approveLogID;
4806    } /* }}} */
4807
4808    /**
4809     * Sets approval status of a document content for a user
4810     *
4811     * This method can be used to approve or reject a document content, or
4812     * to reset its approval state. In most cases this function will be
4813     * called by an user, but  an admin may set the approval for
4814     * somebody else.
4815     * It is first checked if the user is in the list of approvers at all.
4816     * Then it is check if the approval status is already -2. In both cases
4817     * the function returns with an error.
4818     *
4819     * @see SeedDMS_Core_DocumentContent::setReviewByInd()
4820     *
4821     * @param object  $user user in charge for doing the approval
4822     * @param object  $requestUser user actually calling this function
4823     * @param integer $status the status of the approval, possible values are
4824     *        0=unprocessed (maybe used to reset a status)
4825     *        1=approved,
4826     *       -1=rejected,
4827     *       -2=user is deleted (use {link
4828     *       SeedDMS_Core_DocumentContent::delIndApprover} instead)
4829     * @param string $comment approval comment
4830     *
4831     * @return integer|bool new review log id, error code 0 till -4,
4832     * false in case of an sql error
4833     */
4834    public function setApprovalByInd($user, $requestUser, $status, $comment, $file = '') { /* {{{ */
4835        if (!$user || !$requestUser)
4836            return -1;
4837
4838        $db = $this->_document->getDMS()->getDB();
4839
4840        if (!$user->isType('user'))
4841            return -1;
4842
4843        // Check if the user is on the approval list at all.
4844        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
4845        if (is_bool($approvalStatus) && !$approvalStatus) {
4846            return false;
4847        }
4848        if (count($approvalStatus["indstatus"])==0) {
4849            // User is not assigned to approve this document. No action required.
4850            // Return an error.
4851            return -3;
4852        }
4853        $indstatus = array_pop($approvalStatus["indstatus"]);
4854        if ($indstatus["status"]==-2) {
4855            // User has been deleted from approvers
4856            return -4;
4857        }
4858        // Check if the status is really different from the current status
4859        if ($indstatus["status"] == $status)
4860            return 0;
4861
4862        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
4863            `comment`, `date`, `userID`) ".
4864            "VALUES ('". $indstatus["approveID"] ."', '".
4865            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4866            $requestUser->getID() ."')";
4867        $res = $db->getResult($queryStr);
4868        if (is_bool($res) && !$res)
4869            return false;
4870
4871        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4872        if ($file) {
4873            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4874        }
4875        return $approveLogID;
4876    } /* }}} */
4877
4878    /**
4879     * Add another entry to approval log which resets the status
4880     *
4881     * This method will not delete anything from the database, but will add
4882     * a new approval log entry which sets the status to 0. This is only allowed
4883     * if the current status is either 1 (approved) or -1 (rejected).
4884     *
4885     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
4886     * should be called to recalculate the document status.
4887     *
4888     * @param integer $approveid id of approval
4889     * @param SeedDMS_Core_User $requestUser user requesting the removal
4890     * @param string $comment comment
4891     *
4892     * @return integer|bool true if successful, error code < 0,
4893     * false in case of an sql error
4894     */
4895    public function removeApproval($approveid, $requestUser, $comment = '') { /* {{{ */
4896        $db = $this->_document->getDMS()->getDB();
4897
4898        // Check to see if the user can be removed from the approval list.
4899        $approvals = $this->getApprovalStatus();
4900        if (is_bool($approvals) && !$approvals) {
4901            return false;
4902        }
4903        $approvalStatus = null;
4904        foreach ($approvals as $approval) {
4905            if ($approval['approveID'] == $approveid) {
4906                $approvalStatus = $approval;
4907                break;
4908            }
4909        }
4910        if (!$approvalStatus)
4911            return -2;
4912
4913        // The approval log entry may only be removed if the status is 1 or -1
4914        if ($approvalStatus["status"] != 1 && $approvalStatus["status"] != -1)
4915            return -3;
4916
4917        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
4918            `comment`, `date`, `userID`) ".
4919            "VALUES ('". $approvalStatus["approveID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4920            $requestUser->getID() ."')";
4921        $res = $db->getResult($queryStr);
4922        if (is_bool($res) && !$res)
4923            return false;
4924
4925        return true;
4926    } /* }}} */
4927
4928    /**
4929     * Sets approval status of a document content for a group
4930     *
4931     * The functions behaves like
4932     * {link SeedDMS_Core_DocumentContent::setApprovalByInd} but does it for
4933     * a group instead of a user
4934     */
4935    public function setApprovalByGrp($group, $requestUser, $status, $comment, $file = '') { /* {{{ */
4936        if (!$group || !$requestUser)
4937            return -1;
4938
4939        $db = $this->_document->getDMS()->getDB();
4940
4941        if (!$group->isType('group'))
4942            return -1;
4943
4944        // Check if the group is on the approval list at all.
4945        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
4946        if (is_bool($approvalStatus) && !$approvalStatus) {
4947            return false;
4948        }
4949        if (count($approvalStatus)==0) {
4950            // User is not assigned to approve this document. No action required.
4951            // Return an error.
4952            return -3;
4953        }
4954        if ($approvalStatus[0]["status"]==-2) {
4955            // Group has been deleted from approvers
4956            return -4;
4957        }
4958
4959        // Check if the status is really different from the current status
4960        if ($approvalStatus[0]["status"] == $status)
4961            return 0;
4962
4963        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
4964            `comment`, `date`, `userID`) ".
4965            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".
4966            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
4967            $requestUser->getID() ."')";
4968        $res = $db->getResult($queryStr);
4969        if (is_bool($res) && !$res)
4970            return false;
4971
4972        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4973        if ($file) {
4974            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4975        }
4976        return $approveLogID;
4977    } /* }}} */
4978
4979    public function delIndReviewer($user, $requestUser, $msg = '') { /* {{{ */
4980        $db = $this->_document->getDMS()->getDB();
4981
4982        if (!$user->isType('user'))
4983            return -1;
4984
4985        // Check to see if the user can be removed from the review list.
4986        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
4987        if (is_bool($reviewStatus) && !$reviewStatus) {
4988            return false;
4989        }
4990        if (count($reviewStatus["indstatus"])==0) {
4991            // User is not assigned to review this document. No action required.
4992            // Return an error.
4993            return -2;
4994        }
4995        $indstatus = array_pop($reviewStatus["indstatus"]);
4996        if ($indstatus["status"]!=0) {
4997            // User has already submitted a review or has already been deleted;
4998            // return an error.
4999            return -3;
5000        }
5001
5002        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5003            "VALUES ('". $indstatus["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5004        $res = $db->getResult($queryStr);
5005        if (is_bool($res) && !$res) {
5006            return false;
5007        }
5008
5009        return 0;
5010    } /* }}} */
5011
5012    public function delGrpReviewer($group, $requestUser, $msg = '') { /* {{{ */
5013        $db = $this->_document->getDMS()->getDB();
5014
5015        if (!$group->isType('group'))
5016            return -1;
5017
5018        $groupID = $group->getID();
5019
5020        // Check to see if the user can be removed from the review list.
5021        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5022        if (is_bool($reviewStatus) && !$reviewStatus) {
5023            return false;
5024        }
5025        if (count($reviewStatus)==0) {
5026            // User is not assigned to review this document. No action required.
5027            // Return an error.
5028            return -2;
5029        }
5030        if ($reviewStatus[0]["status"]!=0) {
5031            // User has already submitted a review or has already been deleted;
5032            // return an error.
5033            return -3;
5034        }
5035
5036        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5037            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5038        $res = $db->getResult($queryStr);
5039        if (is_bool($res) && !$res) {
5040            return false;
5041        }
5042
5043        return 0;
5044    } /* }}} */
5045
5046    public function delIndApprover($user, $requestUser, $msg = '') { /* {{{ */
5047        $db = $this->_document->getDMS()->getDB();
5048
5049        if (!$user->isType('user'))
5050            return -1;
5051
5052        $userID = $user->getID();
5053
5054        // Check if the user is on the approval list at all.
5055        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5056        if (is_bool($approvalStatus) && !$approvalStatus) {
5057            return false;
5058        }
5059        if (count($approvalStatus["indstatus"])==0) {
5060            // User is not assigned to approve this document. No action required.
5061            // Return an error.
5062            return -2;
5063        }
5064        $indstatus = array_pop($approvalStatus["indstatus"]);
5065        if ($indstatus["status"]!=0) {
5066            // User has already submitted an approval or has already been deleted;
5067            // return an error.
5068            return -3;
5069        }
5070
5071        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5072            "VALUES ('". $indstatus["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5073        $res = $db->getResult($queryStr);
5074        if (is_bool($res) && !$res) {
5075            return false;
5076        }
5077
5078        return 0;
5079    } /* }}} */
5080
5081    public function delGrpApprover($group, $requestUser, $msg = '') { /* {{{ */
5082        $db = $this->_document->getDMS()->getDB();
5083
5084        if (!$group->isType('group'))
5085            return -1;
5086
5087        $groupID = $group->getID();
5088
5089        // Check if the group is on the approval list at all.
5090        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5091        if (is_bool($approvalStatus) && !$approvalStatus) {
5092            return false;
5093        }
5094        if (count($approvalStatus)==0) {
5095            // User is not assigned to approve this document. No action required.
5096            // Return an error.
5097            return -2;
5098        }
5099        if ($approvalStatus[0]["status"]!=0) {
5100            // User has already submitted an approval or has already been deleted;
5101            // return an error.
5102            return -3;
5103        }
5104
5105        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5106            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5107        $res = $db->getResult($queryStr);
5108        if (is_bool($res) && !$res) {
5109            return false;
5110        }
5111
5112        return 0;
5113    } /* }}} */
5114
5115    /**
5116     * Set state of workflow assigned to the document content
5117     *
5118     * @param object $state
5119     */
5120    public function setWorkflowState($state) { /* {{{ */
5121        $db = $this->_document->getDMS()->getDB();
5122
5123        if ($this->_workflow) {
5124            $queryStr = "UPDATE `tblWorkflowDocumentContent` set `state`=". $state->getID() ." WHERE `workflow`=". intval($this->_workflow->getID()). " AND `document`=". intval($this->_document->getID()) ." AND version=". intval($this->_version) ."";
5125            if (!$db->getResult($queryStr)) {
5126                return false;
5127            }
5128            $this->_workflowState = $state;
5129            return true;
5130        }
5131        return false;
5132    } /* }}} */
5133
5134    /**
5135     * Get state of workflow assigned to the document content
5136     *
5137     * @return object/boolean an object of class SeedDMS_Core_Workflow_State
5138     *         or false in case of error, e.g. the version has not a workflow
5139     */
5140    public function getWorkflowState() { /* {{{ */
5141        $db = $this->_document->getDMS()->getDB();
5142
5143        if (!$this->_workflow)
5144            $this->getWorkflow();
5145
5146        if (!$this->_workflow)
5147            return false;
5148
5149        if (!$this->_workflowState) {
5150            $queryStr =
5151                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflowStates` b ON a.`state` = b.`id` WHERE a.`state` IS NOT NULL AND `workflow`=". intval($this->_workflow->getID())
5152                ." AND a.`version`='".$this->_version
5153                ."' AND a.`document` = '". $this->_document->getID() ."' ";
5154            $recs = $db->getResultArray($queryStr);
5155            if (!$recs)
5156                return false;
5157            $this->_workflowState = new SeedDMS_Core_Workflow_State($recs[0]['id'], $recs[0]['name'], $recs[0]['maxtime'], $recs[0]['precondfunc'], $recs[0]['documentstatus']);
5158            $this->_workflowState->setDMS($this->_document->getDMS());
5159        }
5160        return $this->_workflowState;
5161    } /* }}} */
5162
5163    /**
5164     * Assign a workflow to a document content
5165     *
5166     * @param object $workflow
5167     */
5168    public function setWorkflow($workflow, $user) { /* {{{ */
5169        $db = $this->_document->getDMS()->getDB();
5170
5171        $this->getWorkflow();
5172        if ($this->_workflow)
5173            return false;
5174
5175        if ($workflow && is_object($workflow)) {
5176            $db->startTransaction();
5177            $initstate = $workflow->getInitState();
5178            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`workflow`, `document`, `version`, `state`, `date`) VALUES (". $workflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
5179            if (!$db->getResult($queryStr)) {
5180                $db->rollbackTransaction();
5181                return false;
5182            }
5183            $this->_workflow = $workflow;
5184            if (!$this->setStatus(S_IN_WORKFLOW, "Added workflow '".$workflow->getName()."'", $user)) {
5185                $db->rollbackTransaction();
5186                return false;
5187            }
5188            $db->commitTransaction();
5189            return true;
5190        }
5191        return false;
5192    } /* }}} */
5193
5194    /**
5195     * Get workflow assigned to the document content
5196     *
5197     * The method returns the last workflow if one was assigned.
5198     * If the document version is in a sub workflow, it will have
5199     * a never date and therefore will be found first.
5200     *
5201     * @return object/boolean an object of class SeedDMS_Core_Workflow
5202     *         or false in case of error, e.g. the version has not a workflow
5203     */
5204    public function getWorkflow() { /* {{{ */
5205        $db = $this->_document->getDMS()->getDB();
5206
5207        if (!$this->_workflow) {
5208            $queryStr =
5209                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflows` b ON a.`workflow` = b.`id` WHERE a.`version`='".$this->_version
5210                ."' AND a.`document` = '". $this->_document->getID() ."' "
5211                ." ORDER BY `date` DESC LIMIT 1";
5212            $recs = $db->getResultArray($queryStr);
5213            if (is_bool($recs) && !$recs)
5214                return false;
5215            if (!$recs)
5216                return false;
5217            $this->_workflow = new SeedDMS_Core_Workflow($recs[0]['id'], $recs[0]['name'], $this->_document->getDMS()->getWorkflowState($recs[0]['initstate']));
5218            $this->_workflow->setDMS($this->_document->getDMS());
5219        }
5220        return $this->_workflow;
5221    } /* }}} */
5222
5223    /**
5224     * Rewrites the complete workflow log
5225     *
5226     * Attention: this function is highly dangerous.
5227     * It removes an existing workflow log and rewrites it.
5228     * This method was added for importing an xml dump.
5229     *
5230     * @param array $workflowlog new workflow log with the newest log entry first.
5231     * @return boolean true on success, otherwise false
5232     */
5233    public function rewriteWorkflowLog($workflowlog) { /* {{{ */
5234        $db = $this->_document->getDMS()->getDB();
5235
5236        $db->startTransaction();
5237
5238        /* First, remove the old entries */
5239        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `tblWorkflowLog`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowLog`.`version` = '". $this->_version ."'";
5240        if (!$db->getResult($queryStr)) {
5241            $db->rollbackTransaction();
5242            return false;
5243        }
5244
5245        /* Second, insert the new entries */
5246        $workflowlog = array_reverse($workflowlog);
5247        foreach ($workflowlog as $log) {
5248            if (!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
5249                $db->rollbackTransaction();
5250                return false;
5251            }
5252            $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `transition`, `comment`, `date`, `userid`) ".
5253                "VALUES ('".$this->_document->getID() ."', '".(int) $this->_version."', '".(int) $log['workflow']->getID()."', '".(int) $log['transition']->getID()."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
5254            if (!$db->getResult($queryStr)) {
5255                $db->rollbackTransaction();
5256                return false;
5257            }
5258        }
5259
5260        $db->commitTransaction();
5261        return true;
5262    } /* }}} */
5263
5264    /**
5265     * Restart workflow from its initial state
5266     *
5267     * @return boolean true if workflow could be restarted
5268     *         or false in case of error
5269     */
5270    public function rewindWorkflow() { /* {{{ */
5271        $db = $this->_document->getDMS()->getDB();
5272
5273        $this->getWorkflow();
5274
5275        if (!$this->_workflow) {
5276            return true;
5277        }
5278
5279        $db->startTransaction();
5280        $queryStr = "DELETE from `tblWorkflowLog` WHERE `document` = ". $this->_document->getID() ." AND `version` = ".$this->_version." AND `workflow` = ".$this->_workflow->getID();
5281        if (!$db->getResult($queryStr)) {
5282            $db->rollbackTransaction();
5283            return false;
5284        }
5285
5286        $this->setWorkflowState($this->_workflow->getInitState());
5287        $db->commitTransaction();
5288
5289        return true;
5290    } /* }}} */
5291
5292    /**
5293     * Remove workflow
5294     *
5295     * Fully removing a workflow including entries in the workflow log is
5296     * only allowed if the workflow is still its initial state.
5297     * At a later point of time only unlinking the document from the
5298     * workflow is allowed. It will keep any log entries.
5299     * A workflow is unlinked from a document when enterNextState()
5300     * succeeds.
5301     *
5302     * @param object $user user doing initiating the removal
5303     * @param boolean $unlink if true, just unlink the workflow from the
5304     *        document but do not remove the workflow log. The $unlink
5305     *        flag has been added to detach the workflow from the document
5306     *        when it has reached a valid end state
5307     *        (see SeedDMS_Core_DocumentContent::enterNextState())
5308     * @return boolean true if workflow could be removed
5309     *         or false in case of error
5310     */
5311    public function removeWorkflow($user, $unlink = false) { /* {{{ */
5312        $db = $this->_document->getDMS()->getDB();
5313
5314        $this->getWorkflow();
5315
5316        if (!$this->_workflow) {
5317            return true;
5318        }
5319
5320        /* A workflow should always be in a state, but in case it isn't, the
5321         * at least allow to remove the workflow.
5322         */
5323        $currentstate = $this->getWorkflowState();
5324        if (!$currentstate || SeedDMS_Core_DMS::checkIfEqual($this->_workflow->getInitState(), $currentstate) || $unlink == true) {
5325            $db->startTransaction();
5326            if (!$unlink) {
5327                $queryStr =
5328                    "DELETE FROM `tblWorkflowLog` WHERE "
5329                    ."`version`='".$this->_version."' "
5330                    ." AND `document` = '". $this->_document->getID() ."' "
5331                    ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5332                if (!$db->getResult($queryStr)) {
5333                    $db->rollbackTransaction();
5334                    return false;
5335                }
5336            }
5337            $queryStr =
5338                "DELETE FROM `tblWorkflowDocumentContent` WHERE "
5339                ."`version`='".$this->_version."' "
5340                ." AND `document` = '". $this->_document->getID() ."' "
5341                ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5342            if (!$db->getResult($queryStr)) {
5343                $db->rollbackTransaction();
5344                return false;
5345            }
5346            $this->_workflow = null;
5347            $this->_workflowState = null;
5348            $this->verifyStatus(false, $user, 'Workflow removed');
5349            $db->commitTransaction();
5350        }
5351
5352        return true;
5353    } /* }}} */
5354
5355    /**
5356     * Run a sub workflow
5357     *
5358     * @param object $subworkflow
5359     */
5360    public function getParentWorkflow() { /* {{{ */
5361        $db = $this->_document->getDMS()->getDB();
5362
5363        /* document content must be in a workflow */
5364        $this->getWorkflow();
5365        if (!$this->_workflow)
5366            return false;
5367
5368        $queryStr =
5369            "SELECT * FROM `tblWorkflowDocumentContent` WHERE "
5370            ."`version`='".$this->_version."' "
5371            ." AND `document` = '". $this->_document->getID() ."' "
5372            ." AND `workflow` = '". $this->_workflow->getID() ."' ";
5373        $recs = $db->getResultArray($queryStr);
5374        if (is_bool($recs) && !$recs)
5375            return false;
5376        if (!$recs)
5377            return false;
5378
5379        if ($recs[0]['parentworkflow'])
5380            return $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']);
5381
5382        return false;
5383    } /* }}} */
5384
5385    /**
5386     * Run a sub workflow
5387     *
5388     * @param object $subworkflow
5389     */
5390    public function runSubWorkflow($subworkflow) { /* {{{ */
5391        $db = $this->_document->getDMS()->getDB();
5392
5393        /* document content must be in a workflow */
5394        $this->getWorkflow();
5395        if (!$this->_workflow)
5396            return false;
5397
5398        /* The current workflow state must match the sub workflows initial state */
5399        if ($subworkflow->getInitState()->getID() != $this->_workflowState->getID())
5400            return false;
5401
5402        if ($subworkflow) {
5403            $initstate = $subworkflow->getInitState();
5404            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`parentworkflow`, `workflow`, `document`, `version`, `state`, `date`) VALUES (". $this->_workflow->getID(). ", ". $subworkflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
5405            if (!$db->getResult($queryStr)) {
5406                return false;
5407            }
5408            $this->_workflow = $subworkflow;
5409            return true;
5410        }
5411        return true;
5412    } /* }}} */
5413
5414    /**
5415     * Return from sub workflow to parent workflow.
5416     * The method will trigger the given transition
5417     *
5418     * FIXME: Needs much better checking if this is allowed
5419     *
5420     * @param object $user intiating the return
5421     * @param object $transtion to trigger
5422     * @param string comment for the transition trigger
5423     */
5424    public function returnFromSubWorkflow($user, $transition = null, $comment = '') { /* {{{ */
5425        $db = $this->_document->getDMS()->getDB();
5426
5427        /* document content must be in a workflow */
5428        $this->getWorkflow();
5429        if (!$this->_workflow)
5430            return false;
5431
5432        if ($this->_workflow) {
5433            $db->startTransaction();
5434
5435            $queryStr =
5436                "SELECT * FROM `tblWorkflowDocumentContent` WHERE `workflow`=". intval($this->_workflow->getID())
5437                . " AND `version`='".$this->_version
5438                ."' AND `document` = '". $this->_document->getID() ."' ";
5439            $recs = $db->getResultArray($queryStr);
5440            if (is_bool($recs) && !$recs) {
5441                $db->rollbackTransaction();
5442                return false;
5443            }
5444            if (!$recs) {
5445                $db->rollbackTransaction();
5446                return false;
5447            }
5448
5449            $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `workflow` =". intval($this->_workflow->getID())." AND `document` = '". $this->_document->getID() ."' AND `version` = '" . $this->_version."'";
5450            if (!$db->getResult($queryStr)) {
5451                $db->rollbackTransaction();
5452                return false;
5453            }
5454
5455            $this->_workflow = $this->_document->getDMS()->getWorkflow($recs[0]['parentworkflow']);
5456            $this->_workflow->setDMS($this->_document->getDMS());
5457
5458            if ($transition) {
5459                if (false === $this->triggerWorkflowTransition($user, $transition, $comment)) {
5460                    $db->rollbackTransaction();
5461                    return false;
5462                }
5463            }
5464
5465            $db->commitTransaction();
5466        }
5467        return $this->_workflow;
5468    } /* }}} */
5469
5470    /**
5471     * Check if the user is allowed to trigger the transition
5472     * A user is allowed if either the user itself or
5473     * a group of which the user is a member of is registered for
5474     * triggering a transition. This method does not change the workflow
5475     * state of the document content.
5476     *
5477     * @param object $user
5478     * @return boolean true if user may trigger transaction
5479     */
5480    public function triggerWorkflowTransitionIsAllowed($user, $transition) { /* {{{ */
5481        $db = $this->_document->getDMS()->getDB();
5482
5483        if (!$this->_workflow)
5484            $this->getWorkflow();
5485
5486        if (!$this->_workflow)
5487            return false;
5488
5489        if (!$this->_workflowState)
5490            $this->getWorkflowState();
5491
5492        /* Check if the user has already triggered the transition */
5493        $queryStr =
5494            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."' AND `workflow` = ". $this->_workflow->getID(). " AND userid = ".$user->getID();
5495        $queryStr .= " AND `transition` = ".$transition->getID();
5496        $resArr = $db->getResultArray($queryStr);
5497        if (is_bool($resArr) && !$resArr)
5498            return false;
5499
5500        if (count($resArr))
5501            return false;
5502
5503        /* Get all transition users allowed to trigger the transition */
5504        $transusers = $transition->getUsers();
5505        if ($transusers) {
5506            foreach ($transusers as $transuser) {
5507                if ($user->getID() == $transuser->getUser()->getID())
5508                    return true;
5509            }
5510        }
5511
5512        /* Get all transition groups whose members are allowed to trigger
5513         * the transition */
5514        $transgroups = $transition->getGroups();
5515        if ($transgroups) {
5516            foreach ($transgroups as $transgroup) {
5517                $group = $transgroup->getGroup();
5518                if ($group->isMember($user))
5519                    return true;
5520            }
5521        }
5522
5523        return false;
5524    } /* }}} */
5525
5526    /**
5527     * Check if all conditions are met to change the workflow state
5528     * of a document content (run the transition).
5529     * The conditions are met if all explicitly set users and a sufficient
5530     * number of users of the groups have acknowledged the content.
5531     *
5532     * @return boolean true if transaction maybe executed
5533     */
5534    public function executeWorkflowTransitionIsAllowed($transition) { /* {{{ */
5535        if (!$this->_workflow)
5536            $this->getWorkflow();
5537
5538        if (!$this->_workflow)
5539            return false;
5540
5541        if (!$this->_workflowState)
5542            $this->getWorkflowState();
5543
5544        /* Get the Log of transition triggers */
5545        $entries = $this->getWorkflowLog($transition);
5546        if (!$entries)
5547            return false;
5548
5549        /* Get all transition users allowed to trigger the transition
5550         * $allowedusers is a list of all users allowed to trigger the
5551         * transition
5552         */
5553        $transusers = $transition->getUsers();
5554        $allowedusers = array();
5555        foreach ($transusers as $transuser) {
5556            $a = $transuser->getUser();
5557            $allowedusers[$a->getID()] = $a;
5558        }
5559
5560        /* Get all transition groups whose members are allowed to trigger
5561         * the transition */
5562        $transgroups = $transition->getGroups();
5563        foreach ($entries as $entry) {
5564            $loguser = $entry->getUser();
5565            /* Unset each allowed user if it was found in the log */
5566            if (isset($allowedusers[$loguser->getID()]))
5567                unset($allowedusers[$loguser->getID()]);
5568            /* Also check groups if required. Count the group membership of
5569             * each user in the log in the array $gg
5570             */
5571            if ($transgroups) {
5572                $loggroups = $loguser->getGroups();
5573                foreach ($loggroups as $loggroup) {
5574                    if (!isset($gg[$loggroup->getID()]))
5575                        $gg[$loggroup->getID()] = 1;
5576                    else
5577                        $gg[$loggroup->getID()]++;
5578                }
5579            }
5580        }
5581        /* If there are allowed users left, then there some users still
5582         * need to trigger the transition.
5583         */
5584        if ($allowedusers)
5585            return false;
5586
5587        if ($transgroups) {
5588            foreach ($transgroups as $transgroup) {
5589                $group = $transgroup->getGroup();
5590                $minusers = $transgroup->getNumOfUsers();
5591                if (!isset($gg[$group->getID()]))
5592                    return false;
5593                if ($gg[$group->getID()] < $minusers)
5594                    return false;
5595            }
5596        }
5597        return true;
5598    } /* }}} */
5599
5600    /**
5601     * Trigger transition
5602     *
5603     * This method will be deprecated
5604     *
5605     * The method will first check if the user is allowed to trigger the
5606     * transition. If the user is allowed, an entry in the workflow log
5607     * will be added, which is later used to check if the transition
5608     * can actually be processed. The method will finally call
5609     * executeWorkflowTransitionIsAllowed() which checks all log entries
5610     * and does the transitions post function if all users and groups have
5611     * triggered the transition. Finally enterNextState() is called which
5612     * will try to enter the next state.
5613     *
5614     * @param object $user
5615     * @param object $transition
5616     * @param string $comment user comment
5617     * @return boolean/object next state if transition could be triggered and
5618     *         then next state could be entered,
5619     *         true if the transition could just be triggered or
5620     *         false in case of an error
5621     */
5622    public function triggerWorkflowTransition($user, $transition, $comment = '') { /* {{{ */
5623        $db = $this->_document->getDMS()->getDB();
5624
5625        if (!$this->_workflow)
5626            $this->getWorkflow();
5627
5628        if (!$this->_workflow)
5629            return false;
5630
5631        if (!$this->_workflowState)
5632            $this->getWorkflowState();
5633
5634        if (!$this->_workflowState)
5635            return false;
5636
5637        /* Check if the user is allowed to trigger the transition.
5638         */
5639        if (!$this->triggerWorkflowTransitionIsAllowed($user, $transition))
5640            return false;
5641
5642        $state = $this->_workflowState;
5643        $queryStr = "INSERT INTO `tblWorkflowLog` (`document`, `version`, `workflow`, `userid`, `transition`, `date`, `comment`) VALUES (".$this->_document->getID().", ".$this->_version.", " . (int) $this->_workflow->getID() . ", " .(int) $user->getID(). ", ".(int) $transition->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($comment).")";
5644        if (!$db->getResult($queryStr))
5645            return false;
5646
5647        /* Check if this transition is processed. Run the post function in
5648         * that case. A transition is processed when all users and groups
5649         * have triggered it.
5650         */
5651        if ($this->executeWorkflowTransitionIsAllowed($transition)) {
5652            /* run post function of transition */
5653//            echo "run post function of transition ".$transition->getID()."<br />";
5654        }
5655
5656        /* Go into the next state. This will only succeed if the pre condition
5657         * function of that states succeeds.
5658         */
5659        $nextstate = $transition->getNextState();
5660        if ($this->enterNextState($user, $nextstate)) {
5661            return $nextstate;
5662        }
5663        return true;
5664
5665    } /* }}} */
5666
5667    /**
5668     * Enter next state of workflow if possible
5669     *
5670     * The method will check if one of the following states in the workflow
5671     * can be reached.
5672     * It does it by running
5673     * the precondition function of that state. The precondition function
5674     * gets a list of all transitions leading to the state. It will
5675     * determine, whether the transitions has been triggered and if that
5676     * is sufficient to enter the next state. If no pre condition function
5677     * is set, then 1 of n transtions are enough to enter the next state.
5678     *
5679     * If moving in the next state is possible and this state has a
5680     * corresponding document state, then the document state will be
5681     * updated and the workflow will be detached from the document.
5682     *
5683     * @param object $user
5684     * @param object $nextstate
5685     * @return boolean true if the state could be reached
5686     *         false if not
5687     */
5688    public function enterNextState($user, $nextstate) { /* {{{ */
5689
5690            /* run the pre condition of the next state. If it is not set
5691             * the next state will be reached if one of the transitions
5692             * leading to the given state can be processed.
5693             */
5694            if ($nextstate->getPreCondFunc() == '') {
5695                $transitions = $this->_workflow->getPreviousTransitions($nextstate);
5696                foreach ($transitions as $transition) {
5697//                echo "transition ".$transition->getID()." led to state ".$nextstate->getName()."<br />";
5698                    if ($this->executeWorkflowTransitionIsAllowed($transition)) {
5699//                    echo "stepping into next state<br />";
5700                        $this->setWorkflowState($nextstate);
5701
5702                        /* Check if the new workflow state has a mapping into a
5703                         * document state. If yes, set the document state will
5704                         * be updated and the workflow will be removed from the
5705                         * document.
5706                         */
5707                        $docstate = $nextstate->getDocumentStatus();
5708                        if ($docstate == S_RELEASED || $docstate == S_REJECTED) {
5709                            $this->setStatus($docstate, "Workflow has ended", $user);
5710                            /* Detach the workflow from the document, but keep the
5711                             * workflow log
5712                             */
5713                            $this->removeWorkflow($user, true);
5714                            return true ;
5715                        }
5716
5717                        /* make sure the users and groups allowed to trigger the next
5718                         * transitions are also allowed to read the document
5719                         */
5720                        $transitions = $this->_workflow->getNextTransitions($nextstate);
5721                        foreach ($transitions as $tran) {
5722//                            echo "checking access for users/groups allowed to trigger transition ".$tran->getID()."<br />";
5723                            $transusers = $tran->getUsers();
5724                            foreach ($transusers as $transuser) {
5725                                $u = $transuser->getUser();
5726//                                echo $u->getFullName()."<br />";
5727                                if ($this->_document->getAccessMode($u) < M_READ) {
5728                                    $this->_document->addAccess(M_READ, $u->getID(), 1);
5729//                                    echo "granted read access<br />";
5730                                } else {
5731//                                    echo "has already access<br />";
5732                                }
5733                            }
5734                            $transgroups = $tran->getGroups();
5735                            foreach ($transgroups as $transgroup) {
5736                                $g = $transgroup->getGroup();
5737//                                echo $g->getName()."<br />";
5738                                if ($this->_document->getGroupAccessMode($g) < M_READ) {
5739                                    $this->_document->addAccess(M_READ, $g->getID(), 0);
5740//                                    echo "granted read access<br />";
5741                                } else {
5742//                                    echo "has already access<br />";
5743                                }
5744                            }
5745                        }
5746                        return true;
5747                    } else {
5748//                        echo "transition not ready for process now<br />";
5749                    }
5750                }
5751                return false;
5752            } else {
5753                return false;
5754            }
5755
5756    } /* }}} */
5757
5758    /**
5759     * Get the so far logged operations on the document content within the
5760     * workflow. Even after finishing the workflow (when the document content
5761     * does not have workflow set anymore) this function returns the list of all
5762     * log entries.
5763     *
5764     * @return array list of objects
5765     */
5766    public function getWorkflowLog($transition = null) { /* {{{ */
5767        $db = $this->_document->getDMS()->getDB();
5768
5769/*
5770        if (!$this->_workflow)
5771            $this->getWorkflow();
5772
5773        if (!$this->_workflow)
5774            return false;
5775*/
5776        $queryStr =
5777            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
5778        if ($transition)
5779            $queryStr .= " AND `transition` = ".$transition->getID();
5780        $queryStr .= " ORDER BY `date`";
5781        $resArr = $db->getResultArray($queryStr);
5782        if (is_bool($resArr) && !$resArr)
5783            return false;
5784
5785        $workflowlogs = array();
5786        for ($i = 0; $i < count($resArr); $i++) {
5787            $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
5788            $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
5789            $workflowlog->setDMS($this);
5790            $workflowlogs[$i] = $workflowlog;
5791        }
5792
5793        return $workflowlogs;
5794    } /* }}} */
5795
5796    /**
5797     * Get the latest workflow log entry for the document content within the
5798     * workflow. Even after finishing the workflow (when the document content
5799     * does not have workflow set anymore) this function returns the last
5800     * log entry.
5801     *
5802     * @return object
5803     */
5804    public function getLastWorkflowLog() { /* {{{ */
5805        $db = $this->_document->getDMS()->getDB();
5806
5807/*
5808        if (!$this->_workflow)
5809            $this->getWorkflow();
5810
5811        if (!$this->_workflow)
5812            return false;
5813 */
5814        $queryStr =
5815            "SELECT * FROM `tblWorkflowLog` WHERE `version`='".$this->_version ."' AND `document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
5816        $queryStr .= " ORDER BY `id` DESC LIMIT 1";
5817        $resArr = $db->getResultArray($queryStr);
5818        if (is_bool($resArr) && !$resArr)
5819            return false;
5820
5821        $i = 0;
5822        $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
5823        $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
5824        $workflowlog->setDMS($this);
5825
5826        return $workflowlog;
5827    } /* }}} */
5828
5829    /**
5830     * Check if the document content needs an action by a user
5831     *
5832     * This method will return true if document content is in a transition
5833     * which can be triggered by the given user.
5834     *
5835     * @param SeedDMS_Core_User $user
5836     * @return boolean true is action is needed
5837     */
5838    public function needsWorkflowAction($user) { /* {{{ */
5839        $needwkflaction = false;
5840        if ($this->_workflow) {
5841            if (!$this->_workflowState)
5842                $this->getWorkflowState();
5843            $workflowstate = $this->_workflowState;
5844            if ($transitions = $this->_workflow->getNextTransitions($workflowstate)) {
5845                foreach ($transitions as $transition) {
5846                    if ($this->triggerWorkflowTransitionIsAllowed($user, $transition)) {
5847                        $needwkflaction = true;
5848                    }
5849                }
5850            }
5851        }
5852        return $needwkflaction;
5853    } /* }}} */
5854
5855    /**
5856     * Checks the internal data of the document version and repairs it.
5857     * Currently, this function only repairs a missing filetype
5858     *
5859     * @return boolean true on success, otherwise false
5860     */
5861    public function repair() { /* {{{ */
5862        $dms = $this->_document->getDMS();
5863        $db = $this->_dms->getDB();
5864
5865        if (SeedDMS_Core_File::file_exists($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType)) {
5866            if (strlen($this->_fileType) < 2) {
5867                switch($this->_mimeType) {
5868                case "application/pdf":
5869                case "image/png":
5870                case "image/gif":
5871                case "image/jpg":
5872                    $expect = substr($this->_mimeType, -3, 3);
5873                    if ($this->_fileType != '.'.$expect) {
5874                        $db->startTransaction();
5875                        $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` = ". $this->_id;
5876                        $res = $db->getResult($queryStr);
5877                        if ($res) {
5878                            if (!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) {
5879                                $db->rollbackTransaction();
5880                            } else {
5881                                $db->commitTransaction();
5882                            }
5883                        } else {
5884                            $db->rollbackTransaction();
5885                        }
5886                    }
5887                    break;
5888                }
5889            }
5890        } elseif (SeedDMS_Core_File::file_exists($this->_document->getDir() . $this->_version . '.')) {
5891            echo "no file";
5892        } else {
5893            echo $this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType;
5894        }
5895        return true;
5896    } /* }}} */
5897
5898} /* }}} */
5899
5900
5901/**
5902 * Class to represent a link between two document
5903 *
5904 * Document links are to establish a reference from one document to
5905 * another document. The owner of the document link may not be the same
5906 * as the owner of one of the documents.
5907 * Use {@see SeedDMS_Core_Document::addDocumentLink()} to add a reference
5908 * to another document.
5909 *
5910 * @category   DMS
5911 * @package    SeedDMS_Core
5912 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
5913 *             Uwe Steinmann <uwe@steinmann.cx>
5914 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
5915 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
5916 *             2010-2024 Uwe Steinmann
5917 * @version    Release: @package_version@
5918 */
5919class SeedDMS_Core_DocumentLink { /* {{{ */
5920    /**
5921     * @var integer internal id of document link
5922     */
5923    protected $_id;
5924
5925    /**
5926     * @var SeedDMS_Core_Document reference to document this link belongs to
5927     */
5928    protected $_document;
5929
5930    /**
5931     * @var object reference to target document this link points to
5932     */
5933    protected $_target;
5934
5935    /**
5936     * @var integer id of user who is the owner of this link
5937     */
5938    protected $_userID;
5939
5940    /**
5941     * @var object $_user user who is the owner of this link
5942     */
5943    protected $_user;
5944
5945    /**
5946     * @var integer 1 if this link is public, or 0 if is only visible to the owner
5947     */
5948    protected $_public;
5949
5950    /**
5951     * SeedDMS_Core_DocumentLink constructor.
5952     * @param $id
5953     * @param $document
5954     * @param $target
5955     * @param $userID
5956     * @param $public
5957     */
5958    public function __construct($id, $document, $target, $userID, $public) {
5959        $this->_id = $id;
5960        $this->_document = $document;
5961        $this->_target = $target;
5962        $this->_userID = $userID;
5963        $this->_user = null;
5964        $this->_public = $public ? true : false;
5965    }
5966
5967    /**
5968     * Check if this object is of type 'documentlink'.
5969     *
5970     * @param string $type type of object
5971     */
5972    public function isType($type) { /* {{{ */
5973        return $type == 'documentlink';
5974    } /* }}} */
5975
5976    /**
5977     * @return int
5978     */
5979    public function getID() { return $this->_id; }
5980
5981    /**
5982     * @return SeedDMS_Core_Document
5983     */
5984    public function getDocument() {
5985        return $this->_document;
5986    }
5987
5988    /**
5989     * @return object
5990     */
5991    public function getTarget() {
5992        return $this->_target;
5993    }
5994
5995    /**
5996     * @return bool|SeedDMS_Core_User
5997     */
5998    public function getUser() {
5999        if (!isset($this->_user)) {
6000            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
6001        }
6002        return $this->_user;
6003    }
6004
6005    /**
6006     * @return int
6007     */
6008    public function isPublic() { return $this->_public; }
6009
6010    /**
6011     * Returns the access mode similar to a document
6012     *
6013     * There is no real access mode for document links, so this is just
6014     * another way to add more access restrictions than the default restrictions.
6015     * It is only called for public document links, not accessed by the owner
6016     * or the administrator.
6017     *
6018     * @param SeedDMS_Core_User $u user
6019     * @param $source
6020     * @param $target
6021     * @return int either M_NONE or M_READ
6022     */
6023    public function getAccessMode($u, $source, $target) { /* {{{ */
6024        $dms = $this->_document->getDMS();
6025
6026        /* Check if 'onCheckAccessDocumentLink' callback is set */
6027        if (isset($dms->callbacks['onCheckAccessDocumentLink'])) {
6028            foreach ($dms->callbacks['onCheckAccessDocumentLink'] as $callback) {
6029                if (($ret = call_user_func($callback[0], $callback[1], $this, $u, $source, $target)) > 0) {
6030                    return $ret;
6031                }
6032            }
6033        }
6034
6035        return M_READ;
6036    } /* }}} */
6037
6038} /* }}} */
6039
6040/**
6041 * Class to represent a file attached to a document
6042 *
6043 * Beside the regular document content arbitrary files can be attached
6044 * to a document. This is a similar concept as attaching files to emails.
6045 * The owner of the attached file and the document may not be the same.
6046 * Use {@see SeedDMS_Core_Document::addDocumentFile()} to attach a file.
6047 *
6048 * @category   DMS
6049 * @package    SeedDMS_Core
6050 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
6051 *             Uwe Steinmann <uwe@steinmann.cx>
6052 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
6053 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
6054 *             2010-2024 Uwe Steinmann
6055 * @version    Release: @package_version@
6056 */
6057class SeedDMS_Core_DocumentFile { /* {{{ */
6058    /**
6059     * @var integer internal id of document file
6060     */
6061    protected $_id;
6062
6063    /**
6064     * @var SeedDMS_Core_Document reference to document this file belongs to
6065     */
6066    protected $_document;
6067
6068    /**
6069     * @var integer id of user who is the owner of this link
6070     */
6071    protected $_userID;
6072
6073    /**
6074     * @var object user who is the owner of this link
6075     */
6076    protected $_user;
6077
6078    /**
6079     * @var string comment for the attached file
6080     */
6081    protected $_comment;
6082
6083    /**
6084     * @var string date when the file was attached
6085     */
6086    protected $_date;
6087
6088    /**
6089     * @var integer version of document this file is attached to
6090     */
6091    protected $_version;
6092
6093    /**
6094     * @var integer 1 if this link is public, or 0 if is only visible to the owner
6095     */
6096    protected $_public;
6097
6098    /**
6099     * @var string directory where the file is stored. This is the
6100     * document id with a proceding '/'.
6101     * FIXME: looks like this isn't used anymore. The file path is
6102     * constructed by getPath()
6103     */
6104    protected $_dir;
6105
6106    /**
6107     * @var string extension of the original file name with a leading '.'
6108     */
6109    protected $_fileType;
6110
6111    /**
6112     * @var string mime type of the file
6113     */
6114    protected $_mimeType;
6115
6116    /**
6117     * @var string name of the file that was originally uploaded
6118     */
6119    protected $_orgFileName;
6120
6121    /**
6122     * @var string name of the file as given by the user
6123     */
6124    protected $_name;
6125
6126    /**
6127     * SeedDMS_Core_DocumentFile constructor.
6128     * @param $id
6129     * @param $document
6130     * @param $userID
6131     * @param $comment
6132     * @param $date
6133     * @param $dir
6134     * @param $fileType
6135     * @param $mimeType
6136     * @param $orgFileName
6137     * @param $name
6138     * @param $version
6139     * @param $public
6140     */
6141    public function __construct($id, $document, $userID, $comment, $date, $dir, $fileType, $mimeType, $orgFileName, $name, $version, $public) {
6142        $this->_id = $id;
6143        $this->_document = $document;
6144        $this->_userID = $userID;
6145        $this->_user = null;
6146        $this->_comment = $comment;
6147        $this->_date = $date;
6148        $this->_dir = $dir;
6149        $this->_fileType = $fileType;
6150        $this->_mimeType = $mimeType;
6151        $this->_orgFileName = $orgFileName;
6152        $this->_name = $name;
6153        $this->_version = $version;
6154        $this->_public = $public ? true : false;
6155    }
6156
6157    /**
6158     * Check if this object is of type 'documentfile'.
6159     *
6160     * @param string $type type of object
6161     */
6162    public function isType($type) { /* {{{ */
6163        return $type == 'documentfile';
6164    } /* }}} */
6165
6166    /**
6167     * @return int
6168     */
6169    public function getID() { return $this->_id; }
6170
6171    /**
6172     * @return SeedDMS_Core_Document
6173     */
6174    public function getDocument() { return $this->_document; }
6175
6176    /**
6177     * @return int
6178     */
6179    public function getUserID() { return $this->_userID; }
6180
6181    /**
6182     * @return string
6183     */
6184    public function getComment() { return $this->_comment; }
6185
6186    /*
6187     * Set the comment of the document file
6188     *
6189     * @param string $newComment string new comment of document
6190     */
6191    public function setComment($newComment) { /* {{{ */
6192        $db = $this->_document->getDMS()->getDB();
6193
6194        $queryStr = "UPDATE `tblDocumentFiles` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6195        if (!$db->getResult($queryStr))
6196            return false;
6197
6198        $this->_comment = $newComment;
6199        return true;
6200    } /* }}} */
6201
6202    /**
6203     * @return string
6204     */
6205    public function getDate() { return $this->_date; }
6206
6207    /**
6208     * Set creation date of the document file
6209     *
6210     * @param integer $date timestamp of creation date. If false then set it
6211     * to the current timestamp
6212     * @return boolean true on success
6213     */
6214    public function setDate($date = null) { /* {{{ */
6215        $db = $this->_document->getDMS()->getDB();
6216
6217        if (!$date)
6218            $date = time();
6219        else {
6220            if (!is_numeric($date))
6221                return false;
6222        }
6223
6224        $queryStr = "UPDATE `tblDocumentFiles` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
6225        if (!$db->getResult($queryStr))
6226            return false;
6227        $this->_date = $date;
6228        return true;
6229    } /* }}} */
6230
6231    /**
6232     * @return string
6233     */
6234    public function getDir() { return $this->_dir; }
6235
6236    /**
6237     * @return string
6238     */
6239    public function getFileType() { return $this->_fileType; }
6240
6241    /**
6242     * @return string
6243     */
6244    public function getMimeType() { return $this->_mimeType; }
6245
6246    public function getRealMimeType() { /* {{{ */
6247        $dms = $this->_document->getDMS();
6248        if ($storage = $dms->getStorage()) {
6249            $mimetype = $storage->getContentMimetype($this->_document, $this);
6250        } else {
6251            $mimetype = SeedDMS_Core_File::mimetype($dms->contentDir . $this->getPath());
6252        }
6253        return $mimetype;
6254    } /* }}} */
6255
6256    /**
6257     * @return string
6258     */
6259    public function getOriginalFileName() { return $this->_orgFileName; }
6260
6261    /**
6262     * @return string
6263     */
6264    public function getName() { return $this->_name; }
6265
6266    /*
6267     * Set the name of the document file
6268     *
6269     * @param $newComment string new name of document
6270     */
6271    public function setName($newName) { /* {{{ */
6272        $db = $this->_document->getDMS()->getDB();
6273
6274        $queryStr = "UPDATE `tblDocumentFiles` SET `name` = ".$db->qstr($newName)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6275        if (!$db->getResult($queryStr))
6276            return false;
6277
6278        $this->_name = $newName;
6279
6280        return true;
6281    } /* }}} */
6282
6283    /**
6284     * @return bool|SeedDMS_Core_User
6285     */
6286    public function getUser() {
6287        if (!isset($this->_user))
6288            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
6289        return $this->_user;
6290    }
6291
6292    /**
6293     * @return string
6294     */
6295    public function getPath() {
6296        return $this->_document->getDir() . "f" .$this->_id . $this->_fileType;
6297    }
6298
6299    /**
6300     * @return int
6301     */
6302    public function getVersion() { return $this->_version; }
6303
6304    /*
6305     * Set the version of the document file
6306     *
6307     * @param $newComment string new version of document
6308     */
6309    public function setVersion($newVersion) { /* {{{ */
6310        $db = $this->_document->getDMS()->getDB();
6311
6312        if (!is_numeric($newVersion) && $newVersion != '')
6313            return false;
6314
6315        $queryStr = "UPDATE `tblDocumentFiles` SET `version` = ".(int) $newVersion." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6316        if (!$db->getResult($queryStr))
6317            return false;
6318
6319        $this->_version = (int) $newVersion;
6320        return true;
6321    } /* }}} */
6322
6323    /**
6324     * @return int
6325     */
6326    public function isPublic() { return $this->_public; }
6327
6328    /*
6329     * Set the public flag of the document file
6330     *
6331     * @param $newComment string new comment of document
6332     */
6333    public function setPublic($newPublic) { /* {{{ */
6334        $db = $this->_document->getDMS()->getDB();
6335
6336        $queryStr = "UPDATE `tblDocumentFiles` SET `public` = ".($newPublic ? 1 : 0)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
6337        if (!$db->getResult($queryStr))
6338            return false;
6339
6340        $this->_public = $newPublic ? true : false;
6341        return true;
6342    } /* }}} */
6343
6344    /**
6345     * Returns the access mode similar to a document
6346     *
6347     * There is no real access mode for document files, so this is just
6348     * another way to add more access restrictions than the default restrictions.
6349     * It is only called for public document files, not accessed by the owner
6350     * or the administrator.
6351     *
6352     * @param object $u user
6353     * @return integer either M_NONE or M_READ
6354     */
6355    public function getAccessMode($u) { /* {{{ */
6356        $dms = $this->_document->getDMS();
6357
6358        /* Check if 'onCheckAccessDocumentLink' callback is set */
6359        if (isset($this->_dms->callbacks['onCheckAccessDocumentFile'])) {
6360            foreach ($this->_dms->callbacks['onCheckAccessDocumentFile'] as $callback) {
6361                if (($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
6362                    return $ret;
6363                }
6364            }
6365        }
6366
6367        return M_READ;
6368    } /* }}} */
6369
6370} /* }}} */
6371
6372//
6373// Perhaps not the cleanest object ever devised, it exists to encapsulate all
6374// of the data generated during the addition of new content to the database.
6375// The object stores a copy of the new DocumentContent object, the newly assigned
6376// reviewers and approvers and the status.
6377//
6378/**
6379 * Class to represent a list of document contents
6380 *
6381 * @category   DMS
6382 * @package    SeedDMS_Core
6383 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
6384 *             Uwe Steinmann <uwe@steinmann.cx>
6385 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
6386 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
6387 *             2010-2024 Uwe Steinmann
6388 * @version    Release: @package_version@
6389 */
6390class SeedDMS_Core_AddContentResultSet { /* {{{ */
6391
6392    /**
6393     * @var null
6394     */
6395    protected $_indReviewers;
6396
6397    /**
6398     * @var null
6399     */
6400    protected $_grpReviewers;
6401
6402    /**
6403     * @var null
6404     */
6405    protected $_indApprovers;
6406
6407    /**
6408     * @var null
6409     */
6410    protected $_grpApprovers;
6411
6412    /**
6413     * @var
6414     */
6415    protected $_content;
6416
6417    /**
6418     * @var null
6419     */
6420    protected $_status;
6421
6422    /**
6423     * @var SeedDMS_Core_DMS back reference to document management system
6424     */
6425    protected $_dms;
6426
6427    /**
6428     * SeedDMS_Core_AddContentResultSet constructor.
6429     * @param $content
6430     */
6431    public function __construct($content) { /* {{{ */
6432        $this->_content = $content;
6433        $this->_indReviewers = null;
6434        $this->_grpReviewers = null;
6435        $this->_indApprovers = null;
6436        $this->_grpApprovers = null;
6437        $this->_status = null;
6438        $this->_dms = null;
6439    } /* }}} */
6440
6441    /**
6442     * Set dms this object belongs to.
6443     *
6444     * Each object needs a reference to the dms it belongs to. It will be
6445     * set when the object is created.
6446     * The dms has a references to the currently logged in user
6447     * and the database connection.
6448     *
6449     * @param SeedDMS_Core_DMS $dms reference to dms
6450     */
6451    public function setDMS($dms) { /* {{{ */
6452        $this->_dms = $dms;
6453    } /* }}} */
6454
6455    /**
6456     * @param $reviewer
6457     * @param $type
6458     * @param $status
6459     * @return bool
6460     */
6461    public function addReviewer($reviewer, $type, $status) { /* {{{ */
6462        $dms = $this->_dms;
6463
6464        if (!is_object($reviewer) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)) {
6465            return false;
6466        }
6467        if (!strcasecmp($type, "i")) {
6468            if (strcasecmp(get_class($reviewer), $dms->getClassname("user"))) {
6469                return false;
6470            }
6471            if ($this->_indReviewers == null) {
6472                $this->_indReviewers = array();
6473            }
6474            $this->_indReviewers[$status][] = $reviewer;
6475        }
6476        if (!strcasecmp($type, "g")) {
6477            if (strcasecmp(get_class($reviewer), $dms->getClassname("group"))) {
6478                return false;
6479            }
6480            if ($this->_grpReviewers == null) {
6481                $this->_grpReviewers = array();
6482            }
6483            $this->_grpReviewers[$status][] = $reviewer;
6484        }
6485        return true;
6486    } /* }}} */
6487
6488    /**
6489     * @param $approver
6490     * @param $type
6491     * @param $status
6492     * @return bool
6493     */
6494    public function addApprover($approver, $type, $status) { /* {{{ */
6495        $dms = $this->_dms;
6496
6497        if (!is_object($approver) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)) {
6498            return false;
6499        }
6500        if (!strcasecmp($type, "i")) {
6501            if (strcasecmp(get_class($approver), $dms->getClassname("user"))) {
6502                return false;
6503            }
6504            if ($this->_indApprovers == null) {
6505                $this->_indApprovers = array();
6506            }
6507            $this->_indApprovers[$status][] = $approver;
6508        }
6509        if (!strcasecmp($type, "g")) {
6510            if (strcasecmp(get_class($approver), $dms->getClassname("group"))) {
6511                return false;
6512            }
6513            if ($this->_grpApprovers == null) {
6514                $this->_grpApprovers = array();
6515            }
6516            $this->_grpApprovers[$status][] = $approver;
6517        }
6518        return true;
6519    } /* }}} */
6520
6521    /**
6522     * @param $status
6523     * @return bool
6524     */
6525    public function setStatus($status) { /* {{{ */
6526        if (!is_integer($status)) {
6527            return false;
6528        }
6529        if ($status<-3 || $status>3) {
6530            return false;
6531        }
6532        $this->_status = $status;
6533        return true;
6534    } /* }}} */
6535
6536    /**
6537     * @return null
6538     */
6539    public function getStatus() { /* {{{ */
6540        return $this->_status;
6541    } /* }}} */
6542
6543    /**
6544     * @return mixed
6545     */
6546    public function getContent() { /* {{{ */
6547        return $this->_content;
6548    } /* }}} */
6549
6550    /**
6551     * @param $type
6552     * @return array|bool|null
6553     */
6554    public function getReviewers($type) { /* {{{ */
6555        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
6556            return false;
6557        }
6558        if (!strcasecmp($type, "i")) {
6559            return ($this->_indReviewers == null ? array() : $this->_indReviewers);
6560        }
6561        else {
6562            return ($this->_grpReviewers == null ? array() : $this->_grpReviewers);
6563        }
6564    } /* }}} */
6565
6566    /**
6567     * @param $type
6568     * @return array|bool|null
6569     */
6570    public function getApprovers($type) { /* {{{ */
6571        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
6572            return false;
6573        }
6574        if (!strcasecmp($type, "i")) {
6575            return ($this->_indApprovers == null ? array() : $this->_indApprovers);
6576        }
6577        else {
6578            return ($this->_grpApprovers == null ? array() : $this->_grpApprovers);
6579        }
6580    } /* }}} */
6581} /* }}} */